Codeforces Round 917 (Div. 2)

A. Least Product#

  • 存在 a[i]=0min=0,不需要任何操作。
  • 负数个数为偶数(包括0),min=0,把任意一个改为 0
  • 负数个数为奇数,min=a[i],不需要任何操作。
void solve() {
	int n;
	cin >> n;
	vector<int> b, c, d;
	for(int i = 0; i < n; ++ i) {
		int x;
		cin >> x;
		if(x > 0) b.push_back(x);
		else if(x == 0) c.push_back(x);
		else d.push_back(x);
	}
	if(c.size()) puts("0");
	else if(d.size() % 2 == 0) puts("1\n1 0");
	else puts("0");
}

B. Erase First or Second Letter#

枚举每个字符 s[i],统计以当前字符为第一个元素的方案数。
因为第一个元素已经确定,所以只能删 [i+1,j]的子串。
先前出现过的字符直接 continue

void solve() {
	int n;
	string s;
	cin >> n >> s;
	ll ans = 0;
	vector<bool> vis(26, 0);
	for(int i = 0; i < n; ++ i) {
		if(!vis[s[i] - 'a']) {
			vis[s[i] - 'a'] = 1;
			ans += (n - i);
		}
	}
	cout << ans << '\n';
}

C. Watering an Array#

考虑把数组清零后怎么贪。
在若干次操作后,整个数组一定是非增的,因此每一天最多有一个位置满足条件。
显然,每两天就进行一次 reset 操作为最优。


现在回过来处理原始数组。
枚举操作到第 i 天后再清零。
一次清零最多能得到 n 个贡献。
因此当 i>2n 后,一定不如一开始就清零更优,i 枚举到 2n 就行。
注意 i 要严格小于 d,否则没有多余天数给清零操作。

void solve() {
	int n, k, d;
	cin >> n >> k >> d;
	vector<int> a(n + 1), v(k);
	int ans = 0;
	for(int i = 1; i <= n; ++ i) {
		cin >> a[i];
		if(a[i] == i) ++ ans;
	}
	for(int &x : v) cin >> x;
	ans += (d - 1) >> 1;
	for(int i = 0; i < 2 * n; ++ i) {
		if(i == d - 1) break;
		int res = 0;
		for(int j = 1; j <= v[i % k]; ++ j) ++ a[j];
		for(int j = 1; j <= n; ++ j) if(a[j] == j) ++ res;
		res += (d - i - 2) >> 1;
		ans = max(ans, res);
	}
	cout << ans << '\n';
} 

D. Yet Another Inversions Problem#

E. Construct Matrix#

一个显然的性质:若得到一组关于 k 的解,则对答案整个取反后能得到 k=n2k 的解。#

首先考虑无解。

  • k 为奇数,无解。
  • n>2k=2k=n22,无解。

我们不妨假设剩下的情况都是有解的。
情况一:n=k,很好构造。

void case1() {	// k == n
	for(int i = 1; i <= n; ++ i) a[i][i] = 1; 
}

情况二:nmod4=0

  • 在一行(列)里添加偶数个 1 不会对原异或值有任何改动。

根据这条性质,容易想到用 k42×2 的单元去填整个网格。

[1111]

void case2() {	// k % 4 == 0
	for(int i = 1; i <= n; ++ i) {
		for(int j = 1; j <= n; ++ j) {
			if(!a[i][j] && k) {
				a[i][j] = a[i + 1][j] = a[i][j + 1] = a[i + 1][j + 1] = 1;
				k -= 4;
			}
		}
	}
}

情况三:nmod4=2
去思考如何把情况三转化到情况二。
我们可以在网格的左上角构造如下 4×4 的单元格。

[1100101001100000]

k 整体减去 6 后成功转化为情况二。
按照上述构造方法需要考虑 kmax=n29
如果 k 大于其最大值,则令 k=n2k,最后翻转答案。

void Reverse (){
	for(int i = 1; i <= n; ++ i)
		for(int j = 1; j <= n; ++ j)
			a[i][j] ^= 1;
}
void case3() { // k % 4 == 2
	
	bool f = 0;
	if(k > n * n - 9) f = 1, k = n * n - k;
	
	a[1][1] = a[1][2] = a[2][1] = a[2][3] = a[3][2] = a[3][3] = 1; 
	k -= 6;
	
	for(int i = 1; i <= n; ++ i) {
		for(int j = 1; j <= n; ++ j) {
			if(i <= 4 && j <= 4) continue;
			if(!a[i][j] && k) {
				a[i][j] = a[i + 1][j] = a[i][j + 1] = a[i + 1][j + 1] = 1;
				k -= 4;
			}
		}
	}
	if(f) Reverse();
}

F. Construct Tree#

先把 a 排序。

  • 性质一:对于任意两条边 e1,e2,一定存在至少一条路径同时通过这两条边。

容易得到,若 a[n]+a[n1]>d,则一定无解。

  • 性质二:若存在边集 E(不含 a[n])的子集 S 满足 S=da[n],则一定有解。

S 中所有边构成一条链挂在根节点上
对于剩余的边则一个一个挂上去。
注意:此时已经将性质一中的无解情况去掉了,可以证明此时满足条件。。

  • 性质三:设 S1,S2 为边集 E(不含 a[n])的两个互不重合的子集。若 S1S2 的和都大于 a[n],则一定有解。

S1 中所有边构成一条链挂在根节点上,S2 同样操作。
对于剩余的边则一个一个挂上去。
可以证明此时满足条件。

对于性质三,我们可以用 dp[i][j] 表示 S1 总和为 iS2 总和为 j 的方案是否存在。
对于性质二,可以用 dp[0][na[n]] 表示状态。
联想到 01 背包问题,复杂度 O(nd2),会超时。
由于状态是 01 的,可以用 bitset 压下位,复杂度 O(nd2ω),可以通过。

int n, d, a[N];
bitset<N> dp[N];

void solve() {
	cin >> n >> d;
	for(int i = 1; i <= n; ++ i) cin >> a[i];
	sort(a + 1, a + n + 1);
	if(a[n] + a[n - 1] > d) {
		puts("No");
		return;
	}
	for(int i = 0; i <= d; ++ i) dp[i].reset();
	dp[0][0] = 1;
	for(int i = 1; i < n; ++ i) {
		for(int j = d; j >= 0; -- j) {
			if(j + a[i] <= d) dp[j + a[i]] |= dp[j];
			dp[j] |= dp[j] << a[i];
		}
	}
	bool ok = dp[0][d - a[n]];
	for(int i = a[n]; i <= d - a[n]; ++ i) ok |= dp[i][d - i];
	puts(ok ? "Yes" : "No");
}
posted @   Lu_xZ  阅读(23)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
more_horiz
keyboard_arrow_up dark_mode palette
选择主题
menu
点击右上角即可分享
微信分享提示