Codeforces Round 1054 (Div. 3)

https://codeforces.com/contest/2149

A. Be Positive

题意:给定一个只含-1,0,1的数组,每次可以选择任意一个元素增加1,使得最后整个数组的乘积为正数,需要的最少操作数。

题解:对于任何一个0,都需要操作一次,使其变成1,对于偶数个-1不需要操作,奇数个-1需要将其变成1,操作2次。

void slove()
{
	int n; cin >> n;
	vector<int> a(n + 5);
	int cnt0 = 0, cnt1 = 0;
	for (int i = 1; i <= n; i++) {
		cin >> a[i];
		if (a[i] == 0) {
			cnt0++;
		} else if (a[i] == -1) {
			cnt1++;
		}
	}
	int res = cnt0;
	if (cnt1 & 1) res += 2;
	cout << res << endl;
} 

B. Unconventional Pairs

题意:给定一个数组,数组长度为偶数。可以任意分成 n 个二元组,最小化所有二元组之差的绝对值的最大值。

题解:求二元组之差的绝对值的最大值,要求这个最大值最小,该如何构造这些二元组。升序排序即可。

void slove()
{
	int n; cin >> n;
	vector<int> a(n + 5);
	for (int i = 1; i <= n; i++) cin >> a[i];
	sort(a.begin() + 1, a.begin() + 1 + n);
	int res = 0;
	for (int i = 1; i <= n - 1; i++) {
		res = max(res, abs(a[i + 1] - a[i]));
		i++;
	}
	cout << res << endl;
} 

C. MEX rose

题意:给定一个数组 a,每次可以修改任何一个元素,使其变成 [1,n] 之间的任何一个数,要求 mex(a) = k,最少操作数。

题解:首先求出原数组的 mex 为 cur,那么对于 cur 之前的元素都是存在的,考虑 k > cur。[cur,k - 1] 之间需要填满,k这个元素需要全部修改为其它元素,对于这两个部分的操作取最大值即可,因为k这个元素可以修改为 [cur, k - 1] 之间的元素。如果 k < cur ,那么就是 k的元素个数。

void slove()
{
	int n, k; cin >> n >> k;
	vector<int> a(n + 5), cnt(n + 5);
	for (int i = 1; i <= n; i++) {
		cin >> a[i];
		cnt[a[i]]++;
	}
	sort(a.begin() + 1, a.begin() + 1 + n);
	int cur = -1;
	for (int i = 0; i <= n; i++) {
		if (cnt[i] == 0) {
			cur = i;
			break;
		}	
	}
	int res =  0;
	if (cur == k) {
		cout << res << endl;
		return;
	}
	
	for (int i = cur; i < k; i++) {  // if k < cur => res = 0. 取max也是成立的
		if (cnt[i] == 0) {
			res++;
		}
	}
	res = max(res, cnt[k]);
	cout << res << endl;
} 

D. A and B

题意:给定一个只包含 'a', 'b' 的字符串,每次可以交换两个相邻位置的字符。使得字符串变成存在一个字符全部是连续的。例如 "aabbbbaaa" , "baaaaabbbb"

题解:对于 'a','b' 两种字符,可以分别求将一种字符放在两边的最小操作,然后取min即可。

int n;
string s;
// 将字符 b 放在两边的最小操作数
LL cal(char a, char b) {
	vector<int> cnt_pre(n + 5), cnt_aft(n + 5);  // b 元素个数的前缀和以及后缀和
	cnt_pre[0] = s[0] == b ? 1 : 0;
	for (int i = 1; i < n; i++) {
		cnt_pre[i] = cnt_pre[i - 1] + (s[i] == b);
	}
	for (int i = n - 1; i >= 0; i--) {
		cnt_aft[i] = cnt_aft[i + 1] + (s[i] == b);
	}
	LL res = 0;
	for (int i = 0; i < n; i++) {
		if (s[i] != b) continue;  // 只操作 b	
        // 将 b 放在左边和右边的最小操作数,即前缀a的个数和后缀a的个数。
        // 任意一个b,和左边/右边的a依次交换
		res += min(i + 1 - cnt_pre[i], n - 1 - i + 1 - cnt_aft[i]);  // +1 表示该位置为 b
	}
	return res;
}
 
void slove()
{
	cin >> n;
	cin >> s;
	LL res1 = cal('a', 'b');  
	LL res2 = cal('b', 'a');
	cout << min(res1, res2) << endl;
} 

E. Hidden Knowledge of the Ancients

题意:求子数组恰好有k种元素,长度在[l, r]之间的数量。

题解:滑动窗口,固定左端点,分别求右端点的最大值和最小值。相减即为左端点的贡献。
考虑k种元素,此刻恰好为k种元素,即为右端点的最小值,还需要和长度为 l 取max。
当为 k + 1 种元素时,上一个元素的位置就是为k种元素的右端点的最大值。

void slove()
{
	int n, k, l, r; cin >> n >> k >> l >> r;
	vector<int> a(n + 5);
	for (int i = 1; i <= n; i++) {
		cin >> a[i];
	}
	
	multiset<int> S;
	int cnt = 0;
	vector<int> p1(n + 5), p2(n + 5);  // p1[i] 表示左端点为i,右端点的最小值,p2 为最大值
	for (int i = 1, j = 1; i <= n; i++) {
		while (j <= n && cnt < k && j - i + 1 <= r) {
			if (S.find(a[j]) == S.end()) {
				cnt++;
			}
			S.insert(a[j]);
			j++;
		}
		if (cnt == k) {
			p1[i] = j - 1;
			p1[i] = max(p1[i], i + l - 1);  // 考虑区间长度的最小值
		} else {
			p1[i] = -1;  // 没有满足条件的子数组,贡献为 0
		}
		S.erase(S.find(a[i]));
		if (S.find(a[i]) == S.end()) {
			cnt--;
		}
	}
    
	k++;
	for (int i = 1, j = 1; i <= n; i++) {
		while (j <= n && cnt < k && j - i + 1 <= r) {
			if (S.find(a[j]) == S.end()) {
				cnt++;
			}
			S.insert(a[j]);
			j++;
		}
		if (cnt == k) {
			p2[i] = j - 1;
		} else {
			p2[i] = j;
		}	
		S.erase(S.find(a[i]));
		if (S.find(a[i]) == S.end()) {
			cnt--;
		}
	}
	LL res = 0;
	for (int i = 1; i <= n; i++) {
		if (p1[i] == -1 || p1[i] >= p2[i]) continue;
		res += p2[i] - p1[i];
	}
	cout << res << endl;
} 

F. Nezuko in the Clearing

题意:一个人有初始血量 h ,需要走到 d 处。每次运动走一步,在连续运动时,消耗血量也是连续的。第一次消耗1点,第二次消耗2点....... 休息一次可以增加 1 血量。任意时刻血量都需要大于 0. 从 0 点开始运行,到 d 需要最少的回合。

题意:考虑休息是在同一点,还是分开更优。两种方案增加的血量是相同的,但是分开所消耗的血量更少。所以所有的休息是同一点最多一次。那么每次休息就可以使得运动重新开始(消耗血量再次从 1 开始)。
设休息 x 次,那么可以将整个路段分为 x + 1 段,一共需要走 d 步,分 x + 1 次走,每步的步长为 d / (x + 1),设为 len ,
所消耗的血量为 len * (len + 1) / 2 ,由于是向下取整,对于 r = d % (x + 1) 这些路段,步长为 len + 1。
所以可以计算出休息 x 次,所消耗的血量,和增加的血量。休息次数越多,消耗的血量越少,存在二段性。

int h, d; 
bool check(int x) {
	int r = d % (x + 1), a = d / (x + 1);
	int b = a + 1;
	int cnt = x + 1 - r;
	LL sum = 1LL * cnt * a * (a + 1) / 2 + 1LL * r * b * (b + 1) / 2;
	return sum < h + x;  // 任意时刻血量都需要大于 0
}
void slove()
{
	cin >> h >> d;
	int l = 0, r = d;
	while (l < r) {
		int mid = l + r - 1 >> 1;
		if (check(mid)) r = mid;
		else l = mid + 1;
	}
	cout << l + d << endl;
} 
posted @ 2025-09-29 13:52  yairr  阅读(13)  评论(0)    收藏  举报