牛客周赛 Round 82


A. 夹心饼干

点击查看代码
void solve() {
    std::string s;
    std::cin >> s;
    if (s[0] == s.back()) {
    	std::cout << "YES\n";
    } else {
    	std::cout << "NO\n";
    }
}

B. 食堂大作战1.0 && C. 食堂大作战2.0

题意:有\(n\)个队伍,每个队伍有\(a_i\)人,你要到每个队伍前面各去一次,每时刻每个队伍人数都减\(1\),当你在这个队伍且队伍只有你一个人时你就到了这个队伍前面,然后你可以瞬移到另一个队伍后面。问能否可行以及给出方案。

不算自己的情况,如果一个队伍人数为\(0\)了,那么我们应该在之前排到了这个队伍后面。那么显然如果有两个队伍同时为\(0\),我们无法兼顾这两个队伍。于是每个\(a_i\)只能出现一次。如果每个\(a_i\)只出现一次,那么我们显然可以按从小到大的顺序操作。

点击查看代码
void solve() {
    int n;
    std::cin >> n;
    std::vector<std::pair<int, int>> a(n);
    for (int i = 0; i < n; ++ i) {
    	int x;
    	std::cin >> x;
    	a[i] = {x, i};
    }

    std::sort(a.begin(), a.end());
    for (int i = 0; i + 1 < n; ++ i) {
    	if (a[i].first == a[i + 1].first) {
    		std::cout << "NO\n";
    		return;
    	}
    }

    std::cout << "YES\n";
    for (int i = 0; i < n; ++ i) {
    	std::cout << a[i].second + 1 << " \n"[i == n - 1];
    }
}

D. 小苯的排列计数

题意:给出一个排列的前缀\(min\)数组,求有多少排列满足要求。

发现每次数字变化就代表这个数在这个位置上,那么我们从前往后遍历,找每一段相同的,这一段只有一个是固定的,其他只需要填大于这个数的数就可以了,假设\([i, j]\)都是相同的,那么\(p_i = a_i\)\([i + 1, j]\)的位置选比\(a_i\)大的数就行,不过之前以及填过了\(i-1\)个大于\(a_i\)的数,那么我们总共有\(n - a_i - (i - 1)\)个数可以选,总共有\(j - i\)个位置,用组合数求即可,注意每个数可以任意排列,于是还要乘上一个\(j-i\)的排列。
(代码使用了jiangly的取模类以及组合数板子)

点击查看代码
void solve() {
    int n;
    std::cin >> n;
    std::vector<int> a(n);
    for (int i = 0; i < n; ++ i) {
    	std::cin >> a[i];
    }

    for (int i = 1; i < n; ++ i) {
    	if (a[i] > a[i - 1]) {
    		std::cout << 0 << "\n";
    		return;
    	}
    }

    Z ans = 1;
    for (int i = 0; i < n; ++ i) {
    	int j = i;
    	while (j + 1 < n && a[i] == a[j + 1]) {
    		++ j;
    	}

    	int tot = n - a[i] - i;
    	ans *= comb.binom(tot, j - i) * comb.fac(j - i);
    	i = j;
    }

    std::cout << ans << "\n";
}

E. 和+和

题意:给你两个数组\(a, b\),你要在\(a\)里选\(m\)个数,在\(b\)里选\(m\)个数,满足\(a\)里选的数的下标都小于\(b\)里选的数的下标,使得选出数的总和最小。

用优先队列预处理出\([1, i]\)中从\(a\)里选\(m\)个元素的最小值,和\([i, n]\)\(b\)里选\(m\)个数的最小值。那么就可以枚举\(i\),取两边的值相加。

点击查看代码
void solve() {
    int n, m;
    std::cin >> n >> m;
    std::vector<int> a(n), b(n);
    for (int i = 0; i < n; ++ i) {
    	std::cin >> a[i];
    }

    for (int i = 0; i < n; ++ i) {
    	std::cin >> b[i];
    }

    const i64 inf = 1e18;
    std::priority_queue<int> heap;
    i64 sum = 0;
    std::vector<i64> pre(n, inf), suf(n, inf);
    for (int i = 0; i < n; ++ i) {
    	if (heap.size() == m) {
    		if (a[i] < heap.top()) {
    			sum -= heap.top();
    			heap.pop();
    			sum += a[i];
    			heap.push(a[i]);
    		}
    	} else {
    		heap.push(a[i]);
    		sum += a[i];
    	}

    	if (heap.size() == m) {
    		pre[i] = sum;
    	}
    }

    while (heap.size()) {
    	heap.pop();
    }

    sum = 0;
    for (int i = n - 1; i >= 0; -- i) {
    	if (heap.size() == m) {
    		if (b[i] < heap.top()) {
    			sum -= heap.top();
    			heap.pop();
    			sum += b[i];
    			heap.push(b[i]);
    		}
    	} else {
    		heap.push(b[i]);
    		sum += b[i];
    	}

    	if (heap.size() == m) {
    		suf[i] = sum;
    	}
    }

    i64 ans = inf;
    for (int i = 0; i + 1 < n; ++ i) {
    	ans = std::min(ans, pre[i] + suf[i + 1]);
    }

    std::cout << ans << "\n";
}

F. 怎么写线性SPJ

题意:构造一个长度为\(n\)的数组,满足值域都在\([1, n]\)里,且任意一个子数组都至少有一个数在这个子数组里只出现过一次。求不同数字最少的方案。

手搓一下,长度为\(7\)的答案为\(1,2,1,3,1,2,1\),长度为\(15\)的答案为\(1,2,1,3,1,2,1,4,1,2,1,3,1,2,1\)。发现是一个很对称的数组,并且不同数字只有\(log_{2} n + 1\)个,然后大胆猜这就是最小的种类数,于是就过了。
关于代码实现,可以用递归。也可以发现每个相同数之间的距离是固定了,两个\(x\)之间隔了\(2^x\)的距离。

点击查看代码
void solve() {
    int n;
    std::cin >> n;

    std::vector<int> ans(n);
    int x = 0;
    for (int i = 0; i < n; ++ i) {
    	if (ans[i] == 0) {
    		++ x;
    		for (int j = i; j < n; j += 1 << x) {
    			ans[j] = x;
    		}

    	}
    }

    std::cout << x << "\n";
    for (int i = 0; i < n; ++ i) {
    	std::cout << ans[i] << " \n"[i == n - 1];
    }
}
posted @ 2025-02-23 21:06  maburb  阅读(91)  评论(0)    收藏  举报