2025CSP-S模拟赛12 比赛总结
2025CSP-S模拟赛12
诈骗场。事实上,这场原来的题目顺序是现在的T4T3T2T1。
T1 环游(tour)
这题随便乱搞写个假做法有 80pts。。
直接来看满分做法。
关注到 \(V\) 最多只会变化 \(\lfloor\log V\rfloor\) 次。而且对于一层而言,走的范围都形成一个连续段。那问题就转化为了:能否使每一层的连续段覆盖所有点。
考虑状压 dp,设 \(f_s\) 表示当前选了 \(s\) 这些层,可以覆盖的最长前缀。类似的,我们定义 \(g_s\) 表示可以覆盖的最长后缀。转移的话比较简单,枚举然后刷表转移就行了。考虑到起点是包含在第一层的区间内的,所以此处的转移不包含第一层,也就是状态要比总层数少一。
这边我们发现时间在处理可以覆盖到哪里的时候复杂度略高,可以考虑预处理每一层从某个点开始能向左/向右走到哪里。
考虑统计答案。那无非就是第一层的范围再加上一个前缀一个后缀。考虑枚举前缀的状态,后缀的状态就是补集。然后就是 \((f_s\geq l-1)\cup(g_{\bar{s}}\le r+1)\)。不难理解。
#include <bits/stdc++.h>
using namespace std;
int read() {
int x = 0, f = 1; char ch = getchar();
while (ch < '0' || ch > '9') {
if (ch == '-') f = -1;
ch = getchar();
}
while (ch >= '0' && ch <= '9') {
x = (x << 1) + (x << 3) + (ch ^ 48);
ch = getchar();
}
return x * f;
}
const int N = 2e5 + 10, M = 20;
int n, m, kk;
int a[N], v[N];
int f[N], g[N];
int l[M][N], r[M][N];
void print(int x) {
for (int i = 0; i <= kk; i++) {
cout << ((x >> i) & 1);
}
}
int main() {
n = read(), m = read();
for (int i = 1; i <= n; i++) {
a[i] = read();
}
kk = int(log2(m)) + 1;
v[0] = m;
for (int i = 1; i <= kk; i++) {
v[i] = v[i - 1] / 2;
}
int ms = (1 << kk) - 1;
for (int i = 0; i <= kk; i++) {
l[i][1] = 1;
for (int j = 1 + 1; j <= n; j++) {
if (v[i] >= a[j] - a[j - 1]) l[i][j] = l[i][j - 1];
else l[i][j] = j;
}
r[i][n] = n;
for (int j = n - 1; j >= 1; j--) {
if (v[i] >= a[j + 1] - a[j]) r[i][j] = r[i][j + 1];
else r[i][j] = j;
}
}
memset(g, 0x3f, sizeof(g));
g[0] = n + 1;
for (int st = 1; st <= ms; st++) {
for (int i = 1; i <= kk; i++) {
if (!(st & (1 << i - 1))) continue;
f[st] = max(f[st], r[i][min(n, f[st - (1 << i - 1)] + 1)]);
g[st] = min(g[st], l[i][max(1, g[st - (1 << i - 1)] - 1)]);
}
}
for (int i = 1; i <= n; i = r[0][i] + 1) {
int flag = 0;
for (int st = 0; st <= ms; st++) {
int s = ms - st;
if (f[st] >= i - 1 && g[s] <= r[0][i] + 1) {
flag = 1;
break;
}
}
for (int j = i; j <= r[0][i]; j++) {
if (flag) printf("Possible\n");
else printf("Impossible\n");
}
}
return 0;
}
T2 数塔(pyramid)
二分答案 \(x\)。考虑将 \(<x\) 的数设成 \(0\),\(\geq x\) 的设为 \(1\)。现在要求的就是最顶上的数是 \(0\) 还是 \(1\)。
不难发现,如果原 01 序列中有相邻的两个数相同,那么这两个数上面全是这个数。画图有助于理解。
另外一种情况在于没有相邻的相同的数,那这种情况就是两边的数为答案。
#include <bits/stdc++.h>
using namespace std;
int read() {
int x = 0; char ch = getchar();
while (ch < '0' || ch > '9') ch = getchar();
while (ch >= '0' && ch <= '9') {
x = (x << 1) + (x << 3) + (ch ^ 48);
ch = getchar();
}
return x;
}
const int N = 2e5 + 10;
int n, a[N], b[N];
bool check(int x) {
for (int i = 1; i <= n; i++) {
b[i] = (a[i] >= x);
}
for (int i = n / 2, j = n / 2 + 2; i >= 1 && j <= n; i--, j++) {
if (b[i] == b[i + 1]) return b[i];
if (b[j] == b[j - 1]) return b[j];
}
return b[1];
}
int solve() {
n = read();
n = 2 * n - 1;
for (int i = 1; i <= n; i++) {
a[i] = read();
}
int l = 1, r = n, ans = 0;
while (l <= r) {
int mid = (l + r) / 2;
if (check(mid)) {
ans = mid;
l = mid + 1;
} else {
r = mid - 1;
}
}
printf("%d\n", ans);
return 0;
}
int main() {
int qq = read();
while (qq--) {
solve();
}
return 0;
}
T3 二择(choice)
这题思路也不是那么难,也不知道为什么没人做出来。
我们注意到大小为 \(n\) 的匹配和大小为 \(n\) 的独立集加起来共有 \(3n\) 个点。
考虑求出一组极大匹配(此处不一定非要求最大匹配,一条一条往进加,能加就加),这样剩下来的点集一定是独立集。不难发现,这个匹配和这个独立集必然有至少一个满足条件。
#include <bits/stdc++.h>
using namespace std;
int read() {
int x = 0; char ch = getchar();
while (ch < '0' || ch > '9') ch = getchar();
while (ch >= '0' && ch <= '9') {
x = (x << 1) + (x << 3) + (ch ^ 48);
ch = getchar();
}
return x;
}
const int N = 1500000 + 10;
int n, m;
int f[N], ans[N], tot;
int solve() {
n = read(), m = read();
if (n == 0) {
printf("Beta1\n");
return 0;
}
for (int i = 1; i <= 3 * n; i++) f[i] = 0;
tot = 0;
for (int i = 1; i <= m; i++) {
int x = read(), y = read();
if (!f[x] && !f[y]) {
f[x] = f[y] = 1;
ans[++tot] = i;
}
}
if (tot >= n) {
printf("Beta2\n");
for (int i = 1; i <= n; i++) printf("%d ", ans[i]);
printf("\n");
} else {
printf("Beta1\n");
tot = 0;
for (int i = 1; i <= 3 * n; i++) {
if (!f[i]) ans[++tot] = i, f[i] = 1;
}
for (int i = 1; i <= n; i++) printf("%d ", ans[i]);
printf("\n");
}
return 0;
}
int main() {
int qq = read();
while (qq--) {
solve();
}
return 0;
}
T4 平衡(balance)
简单的。
我们注意到要使和的极差不超过 1,那么必然位于 \(i\) 和 \(i+n\) 的两个数差必然为 1。
考虑从 \(i=1\) 和 \(i=n+1\) 同时开始填数。要使 \(i\) 到 \(n\) 和 \(n+1\) 到 \(2n\) 的两段数的和之差不超过 1,考虑让他们的差尽可能小。考虑在前一段中填 1,同时在后一段填 2;在前一段填 4,在后一段填 3;在前一段填 5,在后一段填 6。
这是感性理解。测验大样例后证实了想法。
#include <bits/stdc++.h>
using namespace std;
int read() {
int x = 0; char ch = getchar();
while (ch < '0' || ch > '9') ch = getchar();
while (ch >= '0' && ch <= '9') {
x = (x << 1) + (x << 3) + (ch ^ 48);
ch = getchar();
}
return x;
}
const int N = 2e5 + 10;
int n;
int ans;
int a[N];
int solve() {
n = read();
for (int i = 1; i <= n; i++) {
if (i % 2 == 1) {
a[i] = i * 2 - 1;
a[i + n] = i * 2;
} else {
a[i] = i * 2;
a[i + n] = i * 2 - 1;
}
}
int sum = 0;
for (int i = 1; i <= n; i++) {
sum += a[i];
}
int mx = sum, mn = sum;
ans = 1;
for (int i = 2; i <= 2 * n; i++) {
sum -= a[i - 1];
sum += a[(i + n - 1 - 1) % (2 * n) + 1];
mx = max(mx, sum);
mn = min(mn, sum);
if (mx - mn > 1) {
ans = 0;
break;
}
}
if (ans) {
printf("YES\n");
for (int i = 1; i <= 2 * n; i++) {
printf("%d ", a[i]);
}
printf("\n");
} else {
printf("NO\n");
}
return 0;
}
int main() {
int qq = read();
while (qq--) {
solve();
}
return 0;
}
小结
这两天的考试暴露出来很多问题。一个是学期内大部分精力投入文化课,导致手感生疏。另外一个是长期不打比赛,对于比赛经验以及做题技巧的运用显得生疏。这些天的比赛起码先回复到比较佳的一个状态。
对于今天的考试而言,还行吧,中规中矩。T1 考虑有纰漏,要是考虑得再详细准确一些,就不会交那个全 Impossible 的 0pts 的代码而是 80pts 的假做法。对于这种诈骗场,极度考验取舍、对题目难度的判断、对时间的分配。

浙公网安备 33010602011771号