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\) 个,则如上述的方案数即为:
其中,第一项表示有奇数个第 \(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 全部加进去:
- 在 BBBABBBA 的前面加入一个 A 即可得到 1 的贡献。
- 显然,我们在任意一个已有的 A 后面再加入 \(a\) 个连续的 A 即可获得 1 的贡献。
然后再考虑把剩余的 B 加进去,与 A 类似:
- 在 BBBABBBA 的后面加入一个 B 即可得到 1 的贡献。
- 例如 \(c=3,b=4\),那么我们只需要在每段已有的 BBB 后面加入 BB 即可再获得 1 的贡献。
- 在 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
容易看出,这是一个分段函数:
但是我们发现这个东西是带修改操作的,所以我们可以用数据结构维护这个分段函数。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;
}

浙公网安备 33010602011771号