2025CSP-S模拟赛11 比赛总结
2025CSP-S模拟赛11
T1 异或
10pts~50pts
简单的暴力。基础的暴力统计,或者拿数据结构简单维护。
80pts
这一档部分分是三角形直接到底。考虑乱搞吧。正解是三角形差分。考试时想到了一种奇奇怪怪的方法搞出了这一档,看代码吧。
for (int i = 1; i <= qq; i++) {
int x = read(), y = read(), l = read(), v = read();
int a = min(n + 1, x + l), b = min(n + 1, y + l);
t[x][y] += v;
t[a][b] -= v;
}
for (int j = 1; j <= n; j++) {
for (int i = 1; i <= n; i++) {
s[i][j] += t[i][j];
s[i + 1][j] += s[i][j];
t[i + 1][j + 1] += t[i][j];
}
}
int ans = 0;
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++) {
ans ^= s[i][j];
}
}
100pts
正解和上一档很像了。
a
aa
aaa
bbbc
bbbcc
bbbccc
bbbcccc
这样就很直观了。把一个三角形转化成一个大三角形减去一个小三角形再减去一个矩形。其中,大小两个三角形都是到底的,直接和上一档一样的处理方式。至于矩形,考虑使用二维差分进行区间修改。
只不过考试时没有想出来这一档。
#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 = 1e3 + 10, M = 3e5 + 10;
int n, qq;
int t[N][N], s[N][N], f[N][N];
void add(int i, int j, int x, int y, int v) {
f[i][j] += v;
f[x + 1][j] -= v;
f[i][y + 1] -= v;
f[x + 1][y + 1] += v;
}
signed main() {
n = read(), qq = read();
for (int i = 1; i <= qq; i++) {
int x = read(), y = read(), l = read(), v = read();
int a = min(n + 1, x + l), b = min(n + 1, y + l);
t[x][y] += v;
t[a][b] -= v;
add(a, y, n, b - 1, -v);
}
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++) {
f[i][j] = f[i - 1][j] + f[i][j - 1] - f[i - 1][j - 1] + f[i][j];
}
}
for (int j = 1; j <= n; j++) {
for (int i = 1; i <= n; i++) {
s[i][j] += t[i][j];
s[i + 1][j] += s[i][j];
s[i][j] += f[i][j];
t[i + 1][j + 1] += t[i][j];
}
}
int ans = 0;
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++) {
ans ^= s[i][j];
}
}
printf("%lld\n", ans);
return 0;
}
T2 游戏
博弈论。
考虑把这么多种情况的这棵二叉树画出来。很明晰了。
那我们对于解决这道题而言就考虑建出这棵二叉树,然后直接在二叉树上查询。具体做法类似于动态开点线段树。
建树。考虑将元素一个一个插入树中。我们在树中插入一个新值时,首先检查节点是否存在,若不存在则创建新节点。然后模拟题目按照取模是否为零让他分别进入左右子树即可。在叶子节点累加即可。
查询。首先,我们到一个节点时先检索当前节点是否存在,若不存在直接返回 0 即可。根据当前深度,如果是 Alice 在操作则返回左右儿子的较小值,否则 Bob 在操作返回较大值。
考虑保证时间复杂度。对于先手而言,如果每次都选择集合较小的一侧,那么每次操作集合大小至少减半,只需要 \(\log n\) 次操作就必定可以使答案为 0,考虑上“两人轮流操作”这个因素,所以当 \(m>2\times \log n\) 时,答案不可能大于 \(0\)。同理答案也不可能小于 \(0\)。因此当 \(m>2\times\log n\) 时答案一定为 \(0\)。故只在 \(m<\log n\) 时进行如上的二叉树操作,保证时间复杂度为 \(O(n\log n)\)。
#include <bits/stdc++.h>
#define int long long
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;
int n, m, a[N], b[N];
struct Tree {
int s[N * 30], lc[N * 30], rc[N * 30];
int tot;
void insert(int &p, int dep, int v) {
if (!p) p = ++tot;
if (dep > m) {
s[p] += v;
return;
}
if (v % b[dep] != 0) insert(lc[p], dep + 1, v);
else insert(rc[p], dep + 1, v);
}
int query(int p, int dep) {
if (!p) return 0;
if (dep > m) return s[p];
int res1 = query(lc[p], dep + 1);
int res2 = query(rc[p], dep + 1);
if (dep % 2 == 1) return min(res1, res2);
else return max(res1, res2);
}
} tr;
signed main() {
n = read(), m = read();
if (m > 2 * log2(n)) { // max=28
printf("0\n");
return 0;
}
for (int i = 1; i <= n; i++) a[i] = read();
for (int i = 1; i <= m; i++) b[i] = read();
int root = 0;
for (int i = 1; i <= n; i++) {
tr.insert(root, 1, a[i]);
}
printf("%lld\n", tr.query(root, 1));
return 0;
}
T3 连通块
这道题一步一步来。
基本思路
对于小规模的数据直接上暴力代码。
- \(O(n^2)\) 建图。
- 枚举删除的节点。
- dfs 统计最大连通块大小。
初步优化
这边有一些剪枝。
- 我们关注到删除一个点后最大连通块,要么是将原图中最大连通块断成两个中的一个,要么是原图中第二大的连通块。因此,断点断在原图最大连通块上一定是最优的。那么我们可以考虑用并查集找出原图最大连通块以及第二大连通块大小。
- 关注到要使最大连通块能被断成两个,那么断掉的这个点一定是原图上的割点。考虑用 tarjan 处理处原图上的割点并只对割点进行处理。
没写完,先咕着。

浙公网安备 33010602011771号