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;
}

浙公网安备 33010602011771号