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 的假做法。对于这种诈骗场,极度考验取舍、对题目难度的判断、对时间的分配。

posted @ 2025-07-07 16:11  Zctf1088  阅读(47)  评论(0)    收藏  举报