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

2025CSP-S模拟赛3

概述

三个月没打模拟赛,跟吃屎了一样。

T1 光

20pts

暴力枚举四个数,判断是否符合条件,没什么好说的。复杂度 \(O(n^4)\)

70pts

考虑枚举三个数,手动算出第四个数。复杂度 \(O(n^3)\)

90pts

说白了,就是一个假做法。想法是以 4 为步长去枚举三个数,然后算数第四个数。再通过一些神秘的小微调,得到更小的答案。但是玩了 1h 多没有什么结果。

100pts

在网上看到的神秘线性做法。

首先考虑一个贪心,为避免不必要的浪费,每次选择最大的位置加上 \(4\),给相邻的两个位置加上 \(2\),再给对角位置加上 \(1\)

但是这样的做法并不正确。因为加到最后只有不到 \(4\) 个位置需要额外的电量,然而我们依然是四个四个放,就很浪费。那么我们枚举最后每个位置剩下多少,然后再贪心即可。复杂度 \(O(4^4n)\)。不得不说,大佬还是大佬,太牛逼了。

#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 INF = 0x3f3f3f3f;
const int N = 6005;
int w1, w2, w3, w4, res, mx;
int solve(int A, int B, int C, int D) {
	res = 0;
	while (A > 0 || B > 0 || C > 0 || D > 0) {
		mx = max({A, B, C, D});
		if (A == mx) {
			A -= 4, B -= 2, C -= 2, D -= 1;
		} else if (B == mx) {
			B -= 4, A -= 2, D -= 2, C -= 1;
		} else if (C == mx) {
			C -= 4, A -= 2, D -= 2, B -= 1;
		} else {
			D -= 4, B -= 2, C -= 2, A -= 1;
		}
		res += 4; // 只有 4 是实际的贡献,其余的 2,2,1 均为此 4 造成的影响。
	}
	return res;
}
int main() {
	w1 = read(), w2 = read(), w3 = read(), w4 = read();
	int ans = INF;
	int a, b, c, d, A, B, C, D;
	for (a = 0; a < 4; a++) {
		for (b = 0; b < 4; b++) {
			for (c = 0; c < 4; c++) {
				for (d = 0; d < 4; d++) {
					ans = min(ans, a + b + c + d + solve(w1 - a - b / 2 - c / 2, w2 - b - a / 2 - d / 2, w3 - c - a / 2 - d / 2, w4 - d - b / 2 - c / 2));
				}
			}
		}
	}
	printf("%d\n", ans);
	
	return 0;
}

10pts

爆搜即可。

100pts

考虑每一位对答案的贡献。

节点 \(i\) 处要在第 \(k\) 位上对答案有贡献,那么移动后处于 \(i\) 处的所有值中第 \(k\) 位为 1 的值应恰好有奇数个。设该节点与他的儿子共有 \(tot\) 个,其中第 \(k\) 位为 1 的有 \(sum\) 个,则如上述的方案数即为:

\[2^{sum-1}\times2^{n-sum-1}-sum\times2^{n-tot-1} \]

其中,第一项表示有奇数个第 \(k\) 位为 1 的方案数,第二项是减掉不合法的方案,即只有一个数第 \(k\) 位为 1。

另外,由于 1 节点没有父亲,所以要特判。1 节点的式子与上式略有不同。

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

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 MOD = 1e9 + 7;
const int N = 1e5 + 10;
int n, a[N], fa[N];
vector<int> G[N];
int p[N];
signed main() {
	p[0] = 1;
	for (int i = 1; i < N; i++) p[i] = p[i - 1] * 2 % MOD;
	n = read();
	for (int i = 1; i <= n; i++) {
		a[i] = read();
	}
	for (int i = 2; i <= n; i++) {
		fa[i] = read();
		G[fa[i]].push_back(i);
	}
	int ans = 0;
	for (int k = 0; k <= 30; k++) {
		for (int i = 1; i <= n; i++) {
			int tot = G[i].size() + 1;
			if (tot == 1) continue;
			int sum = ((a[i] >> k) & 1);
			for (int j : G[i]) {
				sum += ((a[j] >> k) & 1);
			}
			if (sum == 0) continue;
			if (i == 1) {
				int cnt;
				if ((a[i] >> k) & 1) {
					cnt = ((p[max(0ll, sum - 2)] * p[max(0ll, n - sum)] % MOD - p[max(0ll, n - tot)]) % MOD + MOD) % MOD;
				} else {
					cnt = p[max(0ll, sum - 1)] * p[max(0ll, n - sum - 1)] % MOD;
				}
				ans = (ans + cnt * p[k] % MOD) % MOD;
			} else {
				int cnt = ((p[max(0ll, sum - 1)] * p[max(0ll, n - sum - 1)] % MOD - sum * p[max(0ll, n - tot - 1)] % MOD) % MOD + MOD) % MOD;
				ans = (ans + cnt * p[k] % MOD) % MOD;
			}
		}
	}
	printf("%lld\n", ans);
	
	return 0;
}

字符串

其实这个是简单题。

20pts

这部分 \(n \le 10\),直接 \(O(n!)\) 复杂度的暴力跑了。

50pts

考虑把暴力的 dfs 转成 dp。把 dfs 的参数中,当前序列长度、用了多少个 A、上一个选的啥,这三个参数择出来作为 dp 状态。转移也很简单,枚举从哪里转移过来,把这之前全部搞成 A 或全部搞成 B。就没了。

我认为,写出了暴力 dfs 顺理成章的就可以写出这部分 \(O(n^3)\) 的 dp,毕竟状态和转移都是一样的。很多时候,dfs 是可以转化成 dp 的。

100pts

bobo 的题解给了一个思路。

我们可以,枚举 A 和 B 切换了多少次。假设 \(c=3\),比如说第一次枚举 BBBA,第二次枚举 BBBABBBA……即每一段 B 长为 \(c\),每一段 A 长为 \(1\)。然后此时会剩下一些 A 和 B。

考虑先把剩余的 A 全部加进去:

  1. 在 BBBABBBA 的前面加入一个 A 即可得到 1 的贡献。
  2. 显然,我们在任意一个已有的 A 后面再加入 \(a\) 个连续的 A 即可获得 1 的贡献。

然后再考虑把剩余的 B 加进去,与 A 类似:

  1. 在 BBBABBBA 的后面加入一个 B 即可得到 1 的贡献。
  2. 例如 \(c=3,b=4\),那么我们只需要在每段已有的 BBB 后面加入 BB 即可再获得 1 的贡献。
  3. 在 2 处理完之后,在任意一个已有的 B 后面再加入 \(b\) 个连续的 B 即可获得 1 的贡献。

然后就没了。简单题。

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

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 = 1e5 + 10;
int n, m, a, b, c;
int solve() {
	n = read(), m = read(), a = read(), b = read(), c = read();
	int ans = 1 + (n - 1) / a + (m - 1) / b + 1;
	for (int i = 1; i <= min(n, m / c); i++) {
		int cn = n, cm = m;
		int sum = i * 2;
		cn -= i, cm -= i * c;
		if (cn > 0) {
			sum += 1, cn -= 1;
			sum += cn / a;
		}
		if (c > b) {
			sum += (c - 1) / b * i;
		}
		if (cm > 0) {
			sum += 1, cm -= 1;
			int x = b - (c - 1) % b;
			sum += min(cm / x, i);
			cm = max(0ll, cm - x * i);
			sum += cm / b;
		}
		ans = max(ans, sum);
	}
	printf("%lld\n", ans);
	
	return 0;
}
signed main() {
	int qq = read();
	while (qq--) {
		solve();
	}
	
	return 0;
}

奇怪的函数

5pts

直接按题意说的模拟即可。咱也不知道 bobo 这是什么神奇数据拿到了 30pts 的高分。

100pts

容易看出,这是一个分段函数:

\[F(x)=\left\{\begin{array}{ll} A, & x \le L \\ x+T, & L < x < R \\ B, & x \ge R \end{array}\right. \]

但是我们发现这个东西是带修改操作的,所以我们可以用数据结构维护这个分段函数。bobo 给出的线段树做法我个人认为还是过于费解了。这里给出一种分块做法。

对于修改,把所在块重新跑一遍即可。

对于查询,把每个块的信息处理一遍即可。

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

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 INF = 0x3f3f3f3f3f3f3f3f;
const int N = 1e5 + 10;
int n, qq, a[N], v[N];
int sq, bel[N], st[N], ed[N], up[N], down[N], f[N];
void solve(int x) {
	up[x] = INF, down[x] = -INF, f[x] = 0;
	for (int i = st[x]; i <= ed[x]; i++) {
		if (a[i] == 1) up[x] += v[i], down[x] += v[i], f[x] += v[i];
		if (a[i] == 2) up[x] = min(up[x], v[i]), down[x] = min(down[x], v[i]);
		if (a[i] == 3) up[x] = max(up[x], v[i]), down[x] = max(down[x], v[i]);
	}
}
void init() {
	sq = sqrt(n);
	for (int i = 1; i <= sq; i++) {
		st[i] = n / sq * (i - 1) + 1;
		ed[i] = n / sq * i;
	}
	ed[sq] = n;
	for (int i = 1; i <= n; i++) {
		for (int j = st[i]; j <= ed[i]; j++) bel[j] = i;
		solve(i);
	}
}
int query(int x) {
	for (int i = 1; i <= sq; i++) {
		x += f[i];
		x = min(x, up[i]);
		x = max(x, down[i]);
	}
	return x;
}
signed main() {
	n = read();
	for (int i = 1; i <= n; i++) {
		a[i] = read(), v[i] = read();
	}
	init();
	qq = read();
	while (qq--) {
		int op = read();
		if (op == 4) {
			int x = read();
			printf("%lld\n", query(x));
		} else {
			int p = read(), x = read();
			a[p] = op, v[p] = x;
			solve(bel[p]);
		}
	}
	
	return 0;
}
posted @ 2025-03-22 15:26  Zctf1088  阅读(151)  评论(0)    收藏  举报