2025CSP-S模拟赛28 比赛总结

2025CSP-S模拟赛28

T1 T2 T3 T4
100 AC 25 TLE 20 TLE 15TLE

排名:1/15;总分:160

T1 用了 40 分钟切掉,后三题均为部分分。只写了最基础的一档,头部的人不在,别人又大挂分,于是拿下 rk1。

T1 路径

原图是 DAG,那拓扑排一下然后简单 dp 统计到每个点的路径上能经过的特殊点的最大值是多少。

#include <bits/stdc++.h>
#define il inline

using namespace std;

const int bufsz = 1 << 20;
char ibuf[bufsz], *p1 = ibuf, *p2 = ibuf;
#define getchar() (p1 == p2 && (p2 = (p1 = ibuf) + fread(ibuf, 1, bufsz, stdin), p1 == p2) ? EOF : *p1++)
il int read() {
	int x = 0; char ch = getchar(); bool t = 0;
	while (ch < '0' || ch > '9') {t ^= ch == '-'; ch = getchar();}
	while (ch >= '0' && ch <= '9') {x = (x << 1) + (x << 3) + (ch ^ 48); ch = getchar();}
	return t ? -x : x;
}
const int N = 1e6 + 10;
int n, m, kk;
int tag[N], rudu[N];
vector<int> G[N];
queue<int> q;
int f[N];
il int solve() {
	n = read(), m = read();
	for (int i = 1; i <= n; i++) {
		G[i].clear();
		tag[i] = rudu[i] = f[i] = 0;
	}
	for (int i = 1; i <= m; i++) {
		int x = read(), y = read();
		G[x].push_back(y);
		rudu[y]++;
	}
	kk = read();
	for (int i = 1; i <= kk; i++) {
		int x = read();
		tag[x] = 1;
	}
	while (!q.empty()) q.pop();
	for (int i = 1; i <= n; i++) {
		if (rudu[i] == 0) q.push(i);
	}
	while (!q.empty()) {
		int x = q.front(); q.pop();
		f[x] += tag[x];
		for (int y : G[x]) {
			f[y] = max(f[y], f[x]);
			rudu[y]--;
			if (rudu[y] == 0) q.push(y);
		}
	}
	int ans = 0;
	for (int i = 1; i <= n; i++) {
		ans |= (f[i] == kk);
	}
	if (ans) {
		printf("Yes\n");
	} else {
		printf("No\n");
	}
	
	return 0;
}
int main() {
	int qq = read();
	while (qq--) {
		solve();
	}
	
	return 0;
}

T2 异或

个人认为本题思路比较神仙。

首先将原序列转化成差分序列,那么区间修改就被转化为单点修改。那么一次修改操作即为单点修改或双点修改。下面提到的操作都基于差分序列。

那么我们的目标在于把整个序列全部变为 0。考虑将 \(n\) 个数抽象成 \(n\) 个点,将一次修改抽象成一条边,那么一组合法的方案就由若干个连通块组成。如果设每个连通块的大小为 \(x\),则连通块的大小为 \(x-1\)(全是双点修改)或 \(x\)(存在一个单点修改)。

然后,有一个结论叫做:当且仅当连通块内的数异或和为 \(0\) 时边数为 \(x-1\)。附上题解给的证明:

必要性:显然每次操作都是双点修改,那么整个连通块内的数异或和不会变化,而最终要变为 \(0\),那么 开始时必须是 \(0\)

充分性:考虑一条串起连通块内的所有数的链。每次将链头通过异或自己的方式删掉,下一个数受到影 响后成为新的链头。经历 \(x-1\) 次操作后,最后一个数肯定是 \(0\),无需再操作。

所以说,如果一个子序列内的数异或和为 \(0\) 则将该子序列清零的最小操作次数为子序列大小减一,否则即为该子序列大小。

然后状压即可,转移枚举补集的子集刷表即可。

#include <bits/stdc++.h>
#define int long long
#define il inline

using namespace std;

const int bufsz = 1 << 20;
char ibuf[bufsz], *p1 = ibuf, *p2 = ibuf;
#define getchar() (p1 == p2 && (p2 = (p1 = ibuf) + fread(ibuf, 1, bufsz, stdin), p1 == p2) ? EOF : *p1++)
il int read() {
	int x = 0; char ch = getchar(); bool t = 0;
	while (ch < '0' || ch > '9') {t ^= ch == '-'; ch = getchar();}
	while (ch >= '0' && ch <= '9') {x = (x << 1) + (x << 3) + (ch ^ 48); ch = getchar();}
	return t ? -x : x;
}
const int INF = 0x3f3f3f3f3f3f3f3f;
const int N = 20, M = 140000;
int n, a[N], ms, d[N];
int f[M], sum[M], cnt[M];
signed main() {
	n = read();
	for (int i = 1; i <= n; i++) a[i] = read();
	for (int i = 1; i <= n; i++) {
		d[i] = a[i] ^ a[i - 1];
	}
	ms = (1 << n) - 1;
	for (int st = 0; st <= ms; st++) {
		for (int i = 1; i <= n; i++) {
			if ((st >> i - 1) & 1) {
				sum[st] ^= d[i];
				cnt[st]++;
			}
		}
	}
	for (int i = 0; i <= ms; i++) f[i] = INF;
	f[0] = 0;
	for (int st = 0; st < ms; st++) {
		int s = ms - st;
		for (int ss = s; ss > 0; ss = (ss - 1) & s) {
			f[st | ss] = min(f[st | ss], f[st] + (sum[ss] ? cnt[ss] : cnt[ss] - 1));
		}
	}
	printf("%lld\n", f[ms]);
	
	return 0;
}

T3 距离

T4 花之舞

posted @ 2025-07-28 14:07  Zctf1088  阅读(36)  评论(0)    收藏  举报