2025年8月模拟赛整合
2025年8月模拟赛整合
2025CSP-S模拟赛31
| T1 | T2 | T3 | T4 |
|---|---|---|---|
| 0 RE | 0 RE | 0 RE | 0 RE |
总分:0;排名:17/17
比较神秘,没有加 freopen。挂分为 50,40,30,49。均为部分分。
T1 花海
T1 你这个关注到 \(n\times m \le 2\times 10^5\),我们不妨假设 \(n\le m\),则有 \(n\le \sqrt{2\times 10^5}\)。然后,我们考虑一个 \(n^2m\) 的算法,方法很多,随便口糊一个就好啦。
#include <bits/stdc++.h>
#define il inline
using namespace std;
const int bufsz = 1 << 20;
char ibuf[bufsz], *p1 = ibuf, *p2 = ibuf;
#define getchar() (p1 == p2 && (p2 = (p1 = ibuf) + fread(ibuf, 1, bufsz, stdin), p1 == p2) ? EOF : *p1++)
il int read() {
int x = 0; char ch = getchar(); bool t = 0;
while (ch < '0' || ch > '9') {t ^= ch == '-'; ch = getchar();}
while (ch >= '0' && ch <= '9') {x = (x << 1) + (x << 3) + (ch ^ 48); ch = getchar();}
return t ? -x : x;
}
const int INF = 0x3f3f3f3f;
const int N = 2e5 + 10;
int n, m;
vector<int> a[N], s[N];
il void init() {
for (int i = 0; i <= n + 2; i++) {
for (int j = 0; j <= m + 2; j++) {
a[i].push_back(0);
s[i].push_back(0);
}
}
}
il int getsum(int i, int j, int x, int y) {
return i > x || j > y ? 0 : s[x][y] - s[x][j - 1] - s[i - 1][y] + s[i - 1][j - 1];
}
int main() {
freopen("flower.in", "r", stdin);
freopen("flower.out", "w", stdout);
n = read(), m = read();
int flag = 0;
if (n > m) swap(n, m), flag = 1;
init();
if (!flag) {
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
a[i][j] = read();
}
}
} else {
for (int i = 1; i <= m; i++) {
for (int j = 1; j <= n; j++) {
a[j][i] = read();
}
}
}
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
s[i][j] = s[i - 1][j] + s[i][j - 1] + a[i][j] - s[i - 1][j - 1];
}
}
int ans = 0;
for (int i = 1; i <= n; i++) {
for (int x = i + 1; x <= n; x++) {
int mx = -INF;
for (int y = 1; y <= m; y++) {
ans = max(ans, mx + getsum(i, 1, i, y) + getsum(x, 1, x, y) + getsum(i + 1, y, x - 1, y));
mx = max(mx, getsum(i + 1, y, x - 1, y) - getsum(i, 1, i, y - 1) - getsum(x, 1, x, y - 1));
}
}
}
printf("%d\n", ans);
return 0;
}
T2 划分
考虑 dp。
设 \(f_i\) 表示考虑到 \(i\) 的最小划分代价,令 \(s_i\) 为前缀和。显然有转移:
然后令 \(p\) 表示第一个满足 \(s_j \geq s_i-m\) 的 \(j\),那么式子便化为:
考虑优化。首先,我们发现这个东西不是很好优化,然后考虑一个比较暴力的优化方式,线段树优化。考虑用线段树维护 \(f_j+\max_{k=j+1}^i a_k\)。其实这个不是很好维护。考试时维护这一坨维护了半天没写出来,赛后又写了会儿,前前后后写了 4h。其实有个更为简单好写的方式去维护,就是把这两项拆开维护,维护一个 \(f\) ,再维护一个 \(\max\),再维护一个加和的最小值。这个就非常好写了。
#include <bits/stdc++.h>
#define il inline
#define int long long
using namespace std;
const int bufsz = 1 << 20;
char ibuf[bufsz], *p1 = ibuf, *p2 = ibuf;
#define getchar() (p1 == p2 && (p2 = (p1 = ibuf) + fread(ibuf, 1, bufsz, stdin), p1 == p2) ? EOF : *p1++)
il int read() {
int x = 0; char ch = getchar(); bool t = 0;
while (ch < '0' || ch > '9') {t ^= ch == '-'; ch = getchar();}
while (ch >= '0' && ch <= '9') {x = (x << 1) + (x << 3) + (ch ^ 48); ch = getchar();}
return t ? -x : x;
}
const int INF = 0x3f3f3f3f3f3f3f3f;
const int N = 1e5 + 10;
int n, m, a[N], s[N];
int f[N];
int st[N], head, lst[N];
namespace Seg {
struct node {
int l, r, mn, lazy, mnf, mx;
} tree[4 * N];
#define lc p << 1
#define rc p << 1 | 1
il void pushup(int p) {
tree[p].mn = min(tree[lc].mn, tree[rc].mn);
tree[p].mnf = min(tree[lc].mnf, tree[rc].mnf);
}
il void build(int p, int l, int r) {
tree[p].l = l, tree[p].r = r;
if (l == r) return;
int mid = l + r >> 1;
build(lc, l, mid), build(rc, mid + 1, r);
}
il void pushdown(int p) {
if (!tree[p].lazy) return;
tree[lc].lazy = tree[p].lazy;
tree[rc].lazy = tree[p].lazy;
tree[lc].mx = tree[p].lazy;
tree[rc].mx = tree[p].lazy;
tree[lc].mn = tree[lc].mnf + tree[lc].mx;
tree[rc].mn = tree[rc].mnf + tree[rc].mx;
tree[p].lazy = 0;
}
il void update(int p, int l, int r, int v) {
if (l > r) return;
if (tree[p].l == l && tree[p].r == r) {
tree[p].mx = v;
tree[p].mn = tree[p].mnf + tree[p].mx;
tree[p].lazy = v;
return;
}
pushdown(p);
int mid = tree[p].l + tree[p].r >> 1;
if (r <= mid) update(lc, l, r, v);
else if (l > mid) update(rc, l, r, v);
else update(lc, l, mid, v), update(rc, mid + 1, r, v);
pushup(p);
}
il void modify(int p, int x, int v) {
if (tree[p].l == tree[p].r) {
tree[p].mnf = v;
tree[p].mn = tree[p].mnf + tree[p].mx;
return;
}
pushdown(p);
int mid = tree[p].l + tree[p].r >> 1;
if (x <= mid) modify(lc, x, v);
else modify(rc, x, v);
pushup(p);
}
il int query(int p, int l, int r) {
if (tree[p].l == l && tree[p].r == r) return tree[p].mn;
pushdown(p);
int mid = tree[p].l + tree[p].r >> 1;
if (r <= mid) return query(lc, l, r);
else if (l > mid) return query(rc, l, r);
else return min(query(lc, l, mid), query(rc, mid + 1, r));
}
}
signed main() {
freopen("split.in", "r", stdin);
freopen("split.out", "w", stdout);
n = read(), m = read();
for (int i = 1; i <= n; i++) {
a[i] = read();
s[i] = s[i - 1] + a[i];
}
for (int i = 1; i <= n; i++) {
while (head && a[st[head]] < a[i]) head--;
lst[i] = st[head];
st[++head] = i;
}
Seg::build(1, 0, n);
for (int i = 1; i <= n; i++) {
int p = lower_bound(s, s + 1 + n, s[i] - m) - s;
Seg::update(1, lst[i], i - 1, a[i]);
f[i] = Seg::query(1, p, i - 1);
Seg::modify(1, i, f[i]);
}
printf("%lld\n", f[n]);
return 0;
}
T3 落子无悔
考虑把从上往下删反过来变成从下往上合并。
因为一个儿子对他的一个祖先的贡献是永远不会变的,所以考虑将这些点按一定顺序合并到父亲上,然后把合并后的点看作一个新的点,并在合并的过程中统计贡献。
考虑合并的顺序。对于两个点 \(a,b\) 来讲,令 \(s0,s1\) 分别表示 0 和 1 的数量。那么,如果 \(a\) 排在 \(b\) 前更优,则有 \(s1_a\times s0_b \le s1_b \times s0_a\)。
然后把所有点排一下统计贡献即可。根节点要特判。
#include <bits/stdc++.h>
#define il inline
#define int long long
using namespace std;
const int bufsz = 1 << 20;
char ibuf[bufsz], *p1 = ibuf, *p2 = ibuf;
#define getchar() (p1 == p2 && (p2 = (p1 = ibuf) + fread(ibuf, 1, bufsz, stdin), p1 == p2) ? EOF : *p1++)
il int read() {
int x = 0; char ch = getchar(); bool t = 0;
while (ch < '0' || ch > '9') {t ^= ch == '-'; ch = getchar();}
while (ch >= '0' && ch <= '9') {x = (x << 1) + (x << 3) + (ch ^ 48); ch = getchar();}
return t ? -x : x;
}
const int INF = 0x3f3f3f3f3f3f3f3f;
const int N = 2e5 + 10;
int n, f[N], a[N];
int son[N];
int s0[N], s1[N];
struct node {
int id, s0, s1;
bool operator < (const node & cmp) const {
return s1 * cmp.s0 != cmp.s1 * s0 ? s1 * cmp.s0 < cmp.s1 * s0 : id < cmp.id;
}
};
set<node> st;
int fa[N];
il int getfa(int x) {
return x == fa[x] ? x : fa[x] = getfa(fa[x]);
}
signed main() {
freopen("board.in", "r", stdin);
freopen("board.out", "w", stdout);
n = read();
for (int i = 2; i <= n; i++) {
f[i] = read();
}
for (int i = 1; i <= n; i++) {
a[i] = read();
s0[i] = (a[i] == 0);
s1[i] = (a[i] == 1);
}
for (int i = 2; i <= n; i++) {
st.insert((node){i, s0[i], s1[i]});
}
for (int i = 1; i <= n; i++) {
fa[i] = i;
}
int ans = 0;
while (!st.empty()) {
node t = *(st.begin());
st.erase((node){t.id, t.s0, t.s1});
int x = getfa(f[t.id]);
if (x != 1) st.erase((node){x, s0[x], s1[x]});
ans += s1[x] * t.s0;
s0[x] += t.s0, s1[x] += t.s1;
fa[t.id] = x;
if (x != 1) st.insert((node){x, s0[x], s1[x]});
}
printf("%lld\n", ans);
return 0;
}
T4 体测
2025CSP-S模拟赛33
| T1 | T2 | T3 | T4 |
|---|---|---|---|
| 100 AC | 90 RE | 0 TLE | 30 RE |
总分:220;排名:4/20
T1 切了。T2 本是正确的,但是基环树找环写炸了,我的做法甚至是爆标的。T3 暴力写假了,T4 是部分分。
T1 机器人
按照体面模拟写个 dfs 即可。
#include <bits/stdc++.h>
#define il inline
using namespace std;
const int N = 20 + 5;
int n;
char s[N];
struct node {
int x, y;
bool operator < (const node & cmp) const {
return x != cmp.x ? x < cmp.x : y < cmp.y;
}
};
map<node, int> ans;
int tag[60][60];
il void dfs(int x, int y, int now) {
if (now > n) {
ans[(node){x, y}] = 1;
return;
}
tag[x + N][y + N]--;
int xa = x, ya = y;
if (s[now] == 'L') xa--;
if (s[now] == 'R') xa++;
if (s[now] == 'D') ya--;
if (s[now] == 'U') ya++;
if (tag[xa + N][ya + N] == 1) {
dfs(x, y, now + 1);
} else {
dfs(xa, ya, now + 1);
if (tag[xa + N][ya + N] == 0) {
tag[xa + N][ya + N] = 1;
dfs(x, y, now + 1);
tag[xa + N][ya + N] = 0;
}
}
tag[x + N][y + N]++;
}
int main() {
ios::sync_with_stdio(0); cin.tie(0), cout.tie(0);
cin >> n >> (s + 1);
dfs(0, 0, 1);
cout << ans.size() << "\n";
map<node, int>:: iterator it;
for (it = ans.begin(); it != ans.end(); it++) {
cout << it->first.x << " " << it->first.y << "\n";
}
return 0;
}
T2 旅行
提供一种接近线性的做法。
我们先考虑如果是一棵树如何处理。
我们关注到一条边从一个颜色变到另一种颜色会收到影响的连通块个数是极小的。如果我们令修改前的颜色为 \(a\),修改后的颜色为 \(b\),称这条边两个端点所连接的其他边是否含有该颜色为两头有/没有某种颜色,所以改变一条边产生的贡献就是:
- 把原来一个 \(a\) 的分成两部分,产生 \(+1\) 的贡献(两头都有 \(a\))
- 减少这条边自己即一个 \(a\) 的连通块,产生 \(-1\) 的贡献(两头都没有 \(a\))
- 把原来两个 \(b\) 的连成一部分,产生 \(-1\) 的贡献(两头都有 \(b\))
- 增加这条边自己即一个 \(b\) 的连通块,产生 \(+1\) 的贡献(两头都没有 \(b\))
然后考虑有环的情况。
如果这条边不在环上,那么和上面一样。如果在环上,就需要特判一下。直接说结论了,如果环上除了当前边的所有边的颜色都是 \(a\),那么这条边就不产生(忽略)上面的 1,2 的贡献;如果都是 \(b\),则忽略 3,4。这些结论都挺显然的。
然后就没了啊。初始随便跑一边统计初始联通块的数量,然后每次 \(O(1)\) 更新答案并维护一些信息即可。
#include <bits/stdc++.h>
#define il inline
using namespace std;
const int bufsz = 1 << 20;
char ibuf[bufsz], *p1 = ibuf, *p2 = ibuf;
#define getchar() (p1 == p2 && (p2 = (p1 = ibuf) + fread(ibuf, 1, bufsz, stdin), p1 == p2) ? EOF : *p1++)
il int read() {
int x = 0; char ch = getchar(); bool t = 0;
while (ch < '0' || ch > '9') {t ^= ch == '-'; ch = getchar();}
while (ch >= '0' && ch <= '9') {x = (x << 1) + (x << 3) + (ch ^ 48); ch = getchar();}
return t ? -x : x;
}
const int N = 1e5 + 10;
int n, m, qq;
struct edge {
int y, w, id;
};
vector<edge> G[N];
int u, v, col;
int fa[N];
il int getfa(int x) {
return x == fa[x] ? x : fa[x] = getfa(fa[x]);
}
int cnt;
il void link(int x, int y) {
x = getfa(x), y = getfa(y);
if (x != y) {
fa[y] = x;
cnt--;
}
}
int lst[N];
int tag[N], inst[N], st[N], head, st1[N], head1, vis[N];
il void dfs(int x, int fr) {
vis[x] = 1;
inst[x] = 1;
st[++head] = x;
st1[++head1] = fr;
for (edge i : G[x]) {
if (i.id == fr) continue;
if (inst[i.y]) {
tag[i.id] = 1;
for (int j = head; st[j] != i.y; j--) {
if (inst[st[j]]) tag[st1[j]] = 1;
}
} else if (!vis[i.y]) {
dfs(i.y, i.id);
}
}
head--, head1--;
inst[x] = 0;
}
unordered_map<int, int> mp[N], id[N];
int xx[N], yy[N], ww[N];
int t[N];
int solve() {
n = read(), m = read();
for (int i = 1; i <= n; i++) {
int x = read(), y = read(), w = read();
G[x].push_back({y, w, i});
G[y].push_back({x, w, i});
mp[x][w]++;
mp[y][w]++;
id[x][y] = id[y][x] = i;
xx[i] = x, yy[i] = y, ww[i] = w;
}
for (int i = 1; i <= n; i++) fa[i] = i;
cnt = n;
for (int x = 1; x <= n; x++) {
for (edge i : G[x]) lst[i.w] = 0;
for (edge i : G[x]) {
if (lst[i.w]) link(lst[i.w], i.id);
lst[i.w] = i.id;
}
}
for (int i = 1; i <= n; i++) tag[i] = inst[i] = vis[i] = 0;
head = head1 = 0;
dfs(1, 0);
int len = 0;
for (int i = 1; i <= n; i++) t[i] = 0;
for (int i = 1; i <= n; i++) {
if (tag[i]) {
len++;
t[ww[i]]++;
}
}
for (int k = 1; k <= m; k++) {
u = read(), v = read(), col = read();
int ii = id[u][v];
int w = ww[ii];
mp[u][w]--, mp[v][w]--;
if (tag[ii]) t[w]--;
if (!tag[ii] || (tag[ii] && t[w] < len - 1)) {
if (mp[u][w] > 0 && mp[v][w] > 0) cnt++;
if (mp[u][w] == 0 && mp[v][w] == 0) cnt--;
}
if (!tag[ii] || (tag[ii] && t[col] < len - 1)) {
if (mp[u][col] > 0 && mp[v][col] > 0) cnt--;
if (mp[u][col] == 0 && mp[v][col] == 0) cnt++;
}
printf("%d\n", cnt);
if (tag[ii]) t[col]++;
mp[u][col]++, mp[v][col]++;
ww[ii] = col;
}
for (int i = 1; i <= n; i++) G[i].clear(), mp[i].clear(), id[i].clear();
return 0;
}
int main() {
qq = read();
while (qq--) {
solve();
}
return 0;
}
T3 点餐
T4 无穷括号序列
2025CSP-S模拟赛34
| T1 | T2 | T3 | T4 |
|---|---|---|---|
| 50 TLE | 10 RE | 0 WA | 20 TLE |
总分:80;排名:12/17。
什么情况呢?T1 暴力,T2 暴力写挂了,T3 暴力没写出来,T4 暴力。
T1 酒吧
首先一个 \(O(n^2)\) 的 dp 是显然的。然后考虑优化。我们发现他首先不可单调队列优化,然后发现他不满足决策单调性也不满足凸性,然后发现也难以线段树维护,然后我们就失败了。
考虑正解。首先,显然选 \(1\) 和 \(n\) 是不劣的。我们假设我们选了 \(b_1,b_2,\dots,b_n\) 这些位置,那么 \(ans=\sum(b_{i+1}-b_i)\times(p_{b_i}+p_{b_{i+1}})\)。然后我们把 \((i,p_i)\) 扔到平面直角坐标系上,你就发现这个答案式子可以搞成几个梯形的面积和,然后你就发现答案就是这几个点的连线与 \(x\) 轴组成的封闭图形的面积之和。那么显然选一个上凸包是最优的,然后你维护个凸包就搞完了。不是哥们,这是人类能想到的吗。。
#include <bits/stdc++.h>
#define il inline
#define int long long
using namespace std;
const int bufsz = 1 << 20;
char ibuf[bufsz], *p1 = ibuf, *p2 = ibuf;
#define getchar() (p1 == p2 && (p2 = (p1 = ibuf) + fread(ibuf, 1, bufsz, stdin), p1 == p2) ? EOF : *p1++)
il int read() {
int x = 0; char ch = getchar(); bool t = 0;
while (ch < '0' || ch > '9') {t ^= ch == '-'; ch = getchar();}
while (ch >= '0' && ch <= '9') {x = (x << 1) + (x << 3) + (ch ^ 48); ch = getchar();}
return t ? -x : x;
}
const int N = 5e5 + 10;
int n, a[N];
int q[N], tail;
il int x(int i) {return i;}
il int y(int i) {return a[i];}
il double slope(int i, int j) {
return 1.0 * (y(i) - y(j)) / (x(i) - x(j));
}
signed main() {
freopen("bar.in", "r", stdin);
freopen("bar.out", "w", stdout);
n = read();
for (int i = 1; i <= n; i++) {
a[i] = read();
}
q[++tail] = 1;
for (int i = 2; i <= n; i++) {
while (tail > 1 && slope(q[tail - 1], q[tail]) < slope(q[tail], i)) tail--;
q[++tail] = i;
}
int ans = 0;
for (int i = 1; i < tail; i++) {
ans += (q[i + 1] - q[i]) * (a[q[i + 1]] + a[q[i]]);
}
printf("%lld\n", ans);
return 0;
}
T2 逆转
不会写二分了。
显然答案具有单调性。然后考虑二分这个答案,然后判断是否能达到这个答案。
考虑如何判断。考虑枚举断点。我们在前一半统计至多能选多少个数使得其加和 \(\le mid\),后一半做一样的统计。至于如何统计,搞一个主席树在权值线段树上二分即可。
#include <bits/stdc++.h>
#define il inline
#define int long long
using namespace std;
const int bufsz = 1 << 20;
char ibuf[bufsz], *p1 = ibuf, *p2 = ibuf;
#define getchar() (p1 == p2 && (p2 = (p1 = ibuf) + fread(ibuf, 1, bufsz, stdin), p1 == p2) ? EOF : *p1++)
il int read() {
int x = 0; char ch = getchar(); bool t = 0;
while (ch < '0' || ch > '9') {t ^= ch == '-'; ch = getchar();}
while (ch >= '0' && ch <= '9') {x = (x << 1) + (x << 3) + (ch ^ 48); ch = getchar();}
return t ? -x : x;
}
const int INF = 0x3f3f3f3f3f3f3f3f;
const int N = 3e5 + 10;
int n, kk, a[N], b[N];
int root[N];
struct node {
int l, r, s, val;
} tree[N << 5];
#define lc tree[p].l
#define rc tree[p].r
int tot;
il void build(int &p, int l, int r) {
if (!p) p = ++tot;
if (l == r) return;
int mid = l + r >> 1;
build(lc, l, mid), build(rc, mid + 1, r);
}
il void pushup(int p) {
tree[p].s = tree[lc].s + tree[rc].s;
tree[p].val = tree[lc].val + tree[rc].val;
}
il int update(int p, int l, int r, int x, int v) {
int rt = ++tot;
tree[rt] = tree[p];
if (l == r) {
tree[rt].val += v;
tree[rt].s += v * b[l];
return rt;
}
int mid = l + r >> 1;
if (x <= mid) tree[rt].l = update(lc, l, mid, x, v);
else tree[rt].r = update(rc, mid + 1, r, x, v);
pushup(rt);
return rt;
}
il int query(int p, int q, int l, int r, int x) {
if (l == r) {
return min(x / b[l], tree[q].val - tree[p].val);
}
int mid = l + r >> 1;
int s = tree[tree[q].l].s - tree[tree[p].l].s;
if (x <= s) return query(tree[p].l, tree[q].l, l, mid, x);
else return tree[tree[q].l].val - tree[tree[p].l].val + query(tree[p].r, tree[q].r, mid + 1, r, x - s);
}
il bool check(int num) {
for (int i = 1; i < n; i++) {
if (query(root[0], root[i], 1, n, num) + query(root[i], root[n], 1, n, num) >= kk) {
return true;
}
}
return false;
}
signed main() {
freopen("ace.in", "r", stdin);
freopen("ace.out", "w", stdout);
n = read(), kk = read();
for (int i = 1; i <= n; i++) {
a[i] = read();
b[i] = a[i];
}
sort(b + 1, b + 1 + n);
int ll = unique(b + 1, b + 1 + n) - b - 1;
build(root[0], 1, n);
for (int i = 1; i <= n; i++) {
a[i] = lower_bound(b + 1, b + 1 + ll, a[i]) - b;
root[i] = update(root[i - 1], 1, n, a[i], 1);
}
int L = 0, R = 3e14, ans = 0;
while (L <= R) {
int mid = L + R >> 1;
if (check(mid)) {
ans = mid;
R = mid - 1;
} else {
L = mid + 1;
}
}
printf("%lld\n", ans);
return 0;
}
T3 世界
T4 染色
2025CSP-S模拟赛35
| T1 | T2 | T3 | T4 |
|---|---|---|---|
| 0 RE | 10 TLE | 0 RE | 0 TLE |
总分:10;排名:19/19
事情是这样的。本来,T1 和 T3 没有文件 IO,只有 T2 和 T4 有。然后后来他都加上了,我也没发现。然后我 T1 和 T3 写的 100 多分都没了。
T1 114514
唉这个不想写了。
反正你会发现一个神秘性质,或者叫做统计答案的方式。你就是要找在这个桶中,你把当前的 \(a_i\) 赋值成 1,然后找 \(a_i\) 前最长连续 1 的连续段长度就是这一位能填的数的数量。考场上我是用二分套了个树状数组维护。正解的话你拿个并查集一搞就行了
#include <bits/stdc++.h>
#define il inline
#define int long long
using namespace std;
const int bufsz = 1 << 20;
char ibuf[bufsz], *p1 = ibuf, *p2 = ibuf;
#define getchar() (p1 == p2 && (p2 = (p1 = ibuf) + fread(ibuf, 1, bufsz, stdin), p1 == p2) ? EOF : *p1++)
il int read() {
int x = 0; char ch = getchar(); bool t = 0;
while (ch < '0' || ch > '9') {t ^= ch == '-'; ch = getchar();}
while (ch >= '0' && ch <= '9') {x = (x << 1) + (x << 3) + (ch ^ 48); ch = getchar();}
return t ? -x : x;
}
const int MOD = 1e9 + 7;
const int N = 4e6 + 10;
int n, a[N];
int fa[N];
int getfa(int x) {
return x == fa[x] ? x : fa[x] = getfa(fa[x]);
}
signed main() {
freopen("trans.in", "r", stdin);
freopen("trans.out", "w", stdout);
n = read();
for (int i = 1; i <= n; i++) a[i] = read();
for (int i = 1; i < N; i++) fa[i] = i;
int ans = 1;
for (int i = 1; i <= n; i++) {
int p = getfa(a[i] - 1);
fa[a[i]] = p;
ans = ans * (a[i] - (p + 1) + 1) % MOD;
}
printf("%lld\n", ans);
return 0;
}
T2 沉默乐园
令 \(\max r_i=m\)。
首先,题意就是给出一种构造使得不存在一对真前缀和真后缀的加和相等。然后你可以发现只要统计不相交的两段即可,因为你相交的两段减去中间等价于不相交的两段。
考虑将前缀和数组和后缀和数组进行归并排序,那么我们只需要判断得到的新序列相邻两项是否相等即可。
考虑用 dp 实现这个过程。\(f_{i,j,k}\) 表示左边选到第 \(i\) 位,右边到第 \(j\) 位,且此时的前缀和减去后缀和等于 \(k\)。转移的话你考虑到你是去归并排序,所以你每次是要给更小的那边加上,即:
然后边界显然,\(f_{0,n+1,0}=1\)。答案即为 \(\sum \sum f_{i,i+1,k}\)。
然后搞一个差分优化一下就好了。
#include <bits/stdc++.h>
#define il inline
#define int long long
using namespace std;
const int bufsz = 1 << 20;
char ibuf[bufsz], *p1 = ibuf, *p2 = ibuf;
#define getchar() (p1 == p2 && (p2 = (p1 = ibuf) + fread(ibuf, 1, bufsz, stdin), p1 == p2) ? EOF : *p1++)
il int read() {
int x = 0; char ch = getchar(); bool t = 0;
while (ch < '0' || ch > '9') {t ^= ch == '-'; ch = getchar();}
while (ch >= '0' && ch <= '9') {x = (x << 1) + (x << 3) + (ch ^ 48); ch = getchar();}
return t ? -x : x;
}
const int MOD = 1e9 + 7;
const int N = 50 + 10, M = 2005 + 10;
int n, l[N], r[N];
int f[N][N][M << 1];
il void upd(int &a, int b) {
a = (a + b) % MOD;
}
signed main() {
freopen("orchestra.in", "r", stdin);
freopen("orchestra.out", "w", stdout);
n = read();
for (int i = 1; i <= n; i++) {
l[i] = read(), r[i] = read();
}
f[0][n + 1][0 + M] = 1;
f[0][n + 1][1 + M] = -1;
for (int i = 0; i < n; i++) {
for (int j = n + 1; j > i; j--) {
for (int k = -2000; k <= 2000; k++) {
upd(f[i][j][k + M], f[i][j][k - 1 + M]);
}
for (int k = -2000; k < 0; k++) {
upd(f[i + 1][j][k + l[i + 1] + M], f[i][j][k + M]);
upd(f[i + 1][j][k + r[i + 1] + 1 + M], -f[i][j][k + M]);
if (k + l[i + 1] <= 0 && k + r[i + 1] >= 0) {
upd(f[i + 1][j][0 + M], -f[i][j][k + M]);
upd(f[i + 1][j][1 + M], f[i][j][k + M]);
}
}
for (int k = 0; k <= 2000; k++) {
upd(f[i][j - 1][k - r[j - 1] + M], f[i][j][k + M]);
upd(f[i][j - 1][k - l[j - 1] + 1 + M], -f[i][j][k + M]);
if (k - r[j - 1] <= 0 && k - l[j - 1] >= 0) {
upd(f[i][j - 1][0 + M], -f[i][j][k + M]);
upd(f[i][j - 1][1 + M], f[i][j][k + M]);
}
}
}
}
int ans = 0;
for (int i = 0; i <= n; i++) {
for (int k = -2000; k <= 2000; k++) {
ans = (ans + f[i][i + 1][k + M]) % MOD;
}
}
printf("%lld\n", (ans + MOD) % MOD);
return 0;
}
T3 深黯
T4 终末螺旋
2025CSP-S模拟赛36
| T1 | T2 | T3 | T4 |
|---|---|---|---|
| 40 TLE | 10 WA | 20 TLE | 37 WA |
19/23。
不想说啥了,屁都不会。
反正挺神秘的。思路都不难,但是考场上就是想不到。。qwq
T1 购买饮料
比较神秘,脑子不好。
#include <bits/stdc++.h>
#define il inline
#define int long long
using namespace std;
const int bufsz = 1 << 20;
char ibuf[bufsz], *p1 = ibuf, *p2 = ibuf;
#define getchar() (p1 == p2 && (p2 = (p1 = ibuf) + fread(ibuf, 1, bufsz, stdin), p1 == p2) ? EOF : *p1++)
il int read() {
int x = 0; char ch = getchar(); bool t = 0;
while (ch < '0' || ch > '9') {t ^= ch == '-'; ch = getchar();}
while (ch >= '0' && ch <= '9') {x = (x << 1) + (x << 3) + (ch ^ 48); ch = getchar();}
return t ? -x : x;
}
const int INF = 0x3f3f3f3f3f3f3f3f;
int n, x, a, b;
int solve() {
n = read(), x = read(), a = read(), b = read();
if (b >= a * x) {
if (n / x >= a) printf("-1\n");
else printf("%lld\n", n / x);
return 0;
}
if (n < a * x) {
printf("%lld\n", n / x);
return 0;
}
int k = (n - a * x) / (a * x - b) + 1;
int ans = k * a + (n + k * b - k * a * x) / x;
printf("%lld\n", ans);
return 0;
}
signed main() {
freopen("buy.in", "r", stdin);
freopen("buy.out", "w", stdout);
int qq = read();
while (qq--) {
solve();
}
return 0;
}
T2 多边形
如果存在一个颜色只出现了一次,那么直接把这个点和所有其他点相连即可。否则一定会出现相邻的三个点颜色两两不同,直接把这三个点搞成一个三角形,然后删掉中间那个点重复以上过程即可。
#include <bits/stdc++.h>
#define il inline
using namespace std;
const int N = 1e6 + 10;
int n;
char s[N];
il bool check(int i, int j, int k) {
return
(s[i] == 'R' && s[j] == 'B' && s[k] == 'G') ||
(s[i] == 'R' && s[j] == 'G' && s[k] == 'B') ||
(s[i] == 'B' && s[j] == 'R' && s[k] == 'G') ||
(s[i] == 'B' && s[j] == 'G' && s[k] == 'R') ||
(s[i] == 'G' && s[j] == 'B' && s[k] == 'R') ||
(s[i] == 'G' && s[j] == 'R' && s[k] == 'B');
}
int nxt[N], pre[N];
set<int> st, s1, s2, s3;
il void add(int x, int y) {
cout << x << " " << y << "\n";
}
int main() {
freopen("polygon.in", "r", stdin);
freopen("polygon.out", "w", stdout);
ios::sync_with_stdio(0); cin.tie(0), cout.tie(0);
cin >> n >> (s + 1);
for (int i = 1; i <= n; i++) nxt[i] = i + 1, pre[i] = i - 1;
nxt[n] = 1, pre[1] = n;
for (int i = 1; i <= n; i++) {
if (check(pre[i], i, nxt[i])) st.insert(i);
if (s[i] == 'R') s1.insert(i);
if (s[i] == 'B') s2.insert(i);
if (s[i] == 'G') s3.insert(i);
}
while (s1.size() + s2.size() + s3.size() > 3) {
if (s1.size() == 1) {
int x = *s1.begin();
for (int i = nxt[nxt[x]]; nxt[i] != x; i = nxt[i]) add(x, i);
break;
}
if (s2.size() == 1) {
int x = *s2.begin();
for (int i = nxt[nxt[x]]; nxt[i] != x; i = nxt[i]) add(x, i);
break;
}
if (s3.size() == 1) {
int x = *s3.begin();
for (int i = nxt[nxt[x]]; nxt[i] != x; i = nxt[i]) add(x, i);
break;
}
int x = *st.begin();
st.erase(x);
if (s[x] == 'R') s1.erase(x);
if (s[x] == 'B') s2.erase(x);
if (s[x] == 'G') s3.erase(x);
add(pre[x], nxt[x]);
int i = pre[x], j = nxt[x];
st.erase(i), st.erase(j);
nxt[i] = j, pre[j] = i;
if (check(pre[i], i, nxt[i])) st.insert(i);
if (check(pre[j], j, nxt[j])) st.insert(j);
}
return 0;
}
T3 二分图最大权匹配
T4 飞毯
2025CSP-S模拟赛37
| T1 | T2 | T3 | T4 |
|---|---|---|---|
| 100 AC | 5 WA | 25 RE | 0 RE |
总分:130;排名:17/25。
比较失败。T2 本来输出 Alice 有 70 分的(bushi,然后 T3 写了 50 的部分分,然后挂了一半,T4 部分分的 8 分炸了。
T1 新的阶乘
比较简单,按照题意直接模拟即可。
#include <bits/stdc++.h>
#define il inline
using namespace std;
const int N = 1e7 + 10;
int n;
long long t[N];
int f[N], p[N], tot, mn[N];
il void init() {
f[1] = 1;
for (int i = 2; i < N; i++) {
if (!f[i]) {
p[++tot] = i;
mn[i] = i;
}
for (int j = 1; j <= tot; j++) {
if (i * p[j] >= N) break;
f[i * p[j]] = 1;
mn[i * p[j]] = p[j];
if (i % p[j] == 0) break;
}
}
}
int main() {
ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
init();
cin >> n;
int x, y;
for (int i = n, j = 1; i >= 1; i--, j++) {
x = i;
while (x > 1) {
y = mn[x];
while (x % y == 0) {
x /= y;
t[y] += j;
}
}
}
cout << "f(" << n << ")=";
int flag = 0;
for (int i = 1; i <= tot; i++) {
if (t[p[i]]) {
if (flag) cout << "*";
flag = 1;
cout << p[i];
if (t[p[i]] > 1) {
cout << "^" << t[p[i]];
}
}
}
return 0;
}
T2 博弈树
这边直接说结论,就是如果你询问的这个点是直径的中点,那么 Bob 胜,否则 Alice 胜。贴上题解:
我们考虑先手的位置如果在直径端点的话一定是先手必胜的,否则先手一定不能将 点移动到直径端点,于是我们考虑删除了原树所有直径端点的树,如果初始点在这棵树 上为直径端点那么也一定是先手必胜的,因为先手可以将点移动到另一个直径端点,这 样后手就一定会将点移动到原树的直径端点上,并且移动的长度一定小于原树直径,这 样先手就可以将点移动到原树的另一个直径端点取得胜利。 所以这样我们可以将树的叶子一层一层的删下去,如果最后删剩下一个点那么在这 个点是后手必胜的(因为先手无论如何都会将这个点移动到某一层的直径端点),否则 根据我们之前的证明先手一定可以通过不断将点移动到下一层的直径端点取得胜利。
#include <bits/stdc++.h>
#define il inline
using namespace std;
const int bufsz = 1 << 20;
char ibuf[bufsz], *p1 = ibuf, *p2 = ibuf;
#define getchar() (p1 == p2 && (p2 = (p1 = ibuf) + fread(ibuf, 1, bufsz, stdin), p1 == p2) ? EOF : *p1++)
il int read() {
int x = 0; char ch = getchar(); bool t = 0;
while (ch < '0' || ch > '9') {t ^= ch == '-'; ch = getchar();}
while (ch >= '0' && ch <= '9') {x = (x << 1) + (x << 3) + (ch ^ 48); ch = getchar();}
return t ? -x : x;
}
const int N = 1e5 + 10;
int n, qq;
vector<int> G[N];
int u, v, dep[N];
il void dfs(int x, int fa) {
dep[x] = dep[fa] + 1;
if (dep[x] > dep[u]) u = x;
for (int y : G[x]) {
if (y == fa) continue;
dfs(y, x);
}
}
int f[N];
il void dfs1(int x, int fa) {
dep[x] = dep[fa] + 1;
f[x] = fa;
if (dep[x] > dep[v]) v = x;
for (int y : G[x]) {
if (y == fa) continue;
dfs1(y, x);
}
}
int main() {
n = read(), qq = read();
for (int i = 1; i < n; i++) {
int x = read(), y = read();
G[x].push_back(y);
G[y].push_back(x);
}
dfs(1, 0);
dfs1(u, 0);
int len = dep[v], mid = 0;
if (len % 2 == 1) {
mid = v;
for (int i = 1, x = v; i * 2 < len; i++, x = f[x]) {
mid = f[mid];
}
}
while (qq--) {
int x = read();
if (x == mid) printf("Bob\n");
else printf("Alice\n");
}
return 0;
}
T3 划分
首先,如果前 \(k\) 位都是 0,那么显然是找到第一个不是 0 的位置然后把他及之后的位置作为一段,然后把前面的那么多个 0 分成 \(k-1\) 段最优。
现在考虑前缀不足 \(k\) 个 0 的情况。答案划分一定是这么一个形态,一段长为 \(n-k+1\) 的加上一堆长为 1 的段。那么现在你就找到最大的一段 \(n-k+1\) 的数然后加上其他的 1 就行了。然后你发现这些对于所有最大的划分方案,他的 \(n-k+1\) 的那一段的前 \(n-k\) 位一定相同。那你现在去扫一遍记录一个当前最大的,然后对于每个段你二分哈希去比较两段的大小即可。
#include <bits/stdc++.h>
#define il inline
#define int long long
#define ull unsigned long long
using namespace std;
const int P = 131;
const int MOD = 998244353;
const int N = 2e6 + 10;
int n, kk, a[N];
char S[N];
int fact[N], ny[N];
il int fpow(int a, int x) {
a %= MOD;
int ans = 1;
while (x) {
if (x & 1) ans = ans * a % MOD;
a = a * a % MOD;
x >>= 1;
}
return ans;
}
il int C(int n, int m) {
return n < m ? 0 : fact[n] * ny[m] % MOD * ny[n - m] % MOD;
}
int s[N];
ull p[N], hsh[N];
il ull gethsh(int l, int r) {
return hsh[r] - hsh[l - 1] * p[r - l + 1];
}
signed main() {
ios::sync_with_stdio(0); cin.tie(0), cout.tie(0);
cin >> n >> kk >> (S + 1);
for (int i = 1; i <= n; i++) a[i] = (S[i] == '1');
if (n == kk) {
int ans = 0;
for (int i = 1; i <= n; i++) ans += a[i];
printf("%lld 1\n", ans);
return 0;
}
fact[0] = 1;
for (int i = 1; i <= n; i++) fact[i] = fact[i - 1] * i % MOD;
ny[n] = fpow(fact[n], MOD - 2);
for (int i = n - 1; i >= 0; i--) ny[i] = ny[i + 1] * (i + 1) % MOD;
int id = 0;
while (id < n && a[id + 1] == 0) id++;
if (id >= kk) {
int ans = 0;
for (int i = id + 1; i <= n; i++) {
ans = (ans * 2 % MOD + a[i]) % MOD;
}
int num = 0;
for (int i = kk; i <= id + 1; i++) {
num = (num + C(id, i - 1)) % MOD;
}
printf("%lld %lld\n", ans, num);
return 0;
}
for (int i = 1; i <= n; i++) {
s[i] = (s[i - 1] + a[i]) % MOD;
}
id++;
p[0] = 1;
for (int i = 1; i <= n; i++) p[i] = p[i - 1] * P;
for (int i = 1; i <= n; i++) hsh[i] = hsh[i - 1] * P + a[i];
int len = n - kk + 1;
int mxid = id, num = 1;
for (int i = id + 1; i + len - 1 <= n; i++) {
if (i == 0) continue;
int L = 0, R = len - 1, res = 0;
while (L <= R) {
int mid = L + R >> 1;
if (gethsh(mxid, mxid + mid - 1) == gethsh(i, i + mid - 1)) {
res = mid;
L = mid + 1;
} else {
R = mid - 1;
}
}
if (res == len - 1) {
num = (num + 1) % MOD;
} else if (a[i + res] > a[mxid + res]) {
mxid = i;
num = 1;
}
}
int ans = 0;
for (int i = mxid; i <= mxid + len - 1; i++) {
ans = (ans * 2 % MOD + a[i] % MOD);
}
ans = (ans + s[mxid - 1] + s[n] - s[mxid + len - 1]) % MOD;
printf("%lld %lld\n", ans, num);
return 0;
}
T4 灯笼
比较巧妙的一个 dp。
成天搬人博客也没意思,那这里也不写题解了,这边推荐阅读 题解:P9312 [EGOI 2021] Lanterns / 灯笼
#include <bits/stdc++.h>
#define il inline
#define int long long
using namespace std;
const int bufsz = 1 << 20;
char ibuf[bufsz], *p1 = ibuf, *p2 = ibuf;
#define getchar() (p1 == p2 && (p2 = (p1 = ibuf) + fread(ibuf, 1, bufsz, stdin), p1 == p2) ? EOF : *p1++)
il int read() {
int x = 0; char ch = getchar(); bool t = 0;
while (ch < '0' || ch > '9') {t ^= ch == '-'; ch = getchar();}
while (ch >= '0' && ch <= '9') {x = (x << 1) + (x << 3) + (ch ^ 48); ch = getchar();}
return t ? -x : x;
}
const int INF = 0x3f3f3f3f3f3f3f3f;
const int N = 2005;
int n, kk, h[N];
int p[N], c[N], a[N], b[N];
int id1[N], id2[N];
il bool cmp1(int x, int y) {return a[x] < a[y];}
il bool cmp2(int x, int y) {return b[x] > b[y];}
int la[N], ra[N], lb[N], rb[N];
int f[N][N];
il bool check(int x, int y, int k) {
return la[x] < p[k] && p[k] < ra[x] && lb[y] < p[k] && p[k] < rb[y] && b[k] >= a[x] && a[k] <= b[y];
}
struct node {
int k, f;
il bool operator < (const node & cmp) const {
return f > cmp.f;
}
};
priority_queue<node> qx[N], qy[N];
signed main() {
n = read(), kk = read();
for (int i = 1; i <= n; i++) h[i] = read();
for (int i = 1; i <= kk; i++) {
p[i] = read(), c[i] = read(), a[i] = read(), b[i] = read();
id1[i] = id2[i] = i;
}
sort(id1 + 1, id1 + 1 + kk, cmp1);
sort(id2 + 1, id2 + 1 + kk, cmp2);
for (int i = 1; i <= kk; i++) {
la[i] = p[i]; while (la[i] >= 1 && h[la[i]] >= a[i]) la[i]--;
ra[i] = p[i]; while (ra[i] <= n && h[ra[i]] >= a[i]) ra[i]++;
lb[i] = p[i]; while (lb[i] >= 1 && h[lb[i]] <= b[i]) lb[i]--;
rb[i] = p[i]; while (rb[i] <= n && h[rb[i]] <= b[i]) rb[i]++;
}
for (int i = 1; i <= kk; i++)
for (int j = 1; j <= kk; j++) f[i][j] = INF;
for (int i = 1; i <= kk && a[id1[i]] == a[id1[1]]; i++) {
for (int j = 1; j <= kk && b[id2[j]] == b[id2[1]]; j++) {
f[id1[i]][id2[j]] = 0;
}
}
for (int i = 1; i <= kk; i++) {
for (int j = 1; j <= kk; j++) {
int x = id1[i], y = id2[j];
if (la[x] >= p[y] || p[y] >= ra[x] || lb[y] >= p[x] || p[x] >= rb[y]) continue;
if (a[y] < a[x] && b[x] > b[y]) continue;
if (a[y] < a[x]) f[x][y] = min(f[x][y], f[y][y]);
if (b[x] > b[y]) f[x][y] = min(f[x][y], f[x][x]);
while (!qy[y].empty() && !check(x, y, qy[y].top().k)) qy[y].pop();
if (!qy[y].empty()) f[x][y] = min(f[x][y], qy[y].top().f);
while (!qx[x].empty() && !check(x, y, qx[x].top().k)) qx[x].pop();
if (!qx[x].empty()) f[x][y] = min(f[x][y], qx[x].top().f);
qx[x].push({y, f[x][y] + c[y]});
qy[y].push({x, f[x][y] + c[x]});
}
}
for (int i = 1; i <= kk; i++) {
printf("%lld\n", f[i][i] < INF ? f[i][i] + c[i] : -1);
}
return 0;
}
2025CSP-S模拟赛38
| T1 | T2 | T3 | T4 |
|---|---|---|---|
| 30 TLE | 0 WA | 0 WA | 27 TLE |
总分:57;排名:16/22
T1 玩了很久无果,拼了个暴力。二三不会,T4 是暴力。
T1 黑暗料理
赛时觉得此题很 2-SAT。但是由于对于 2-SAT 板子的理解不足以及并没有意识到:2-SAT 只能求出一种合法的解,并无法求出最优解。然后我们就失败了。
正解来了。考虑到只有 1+1 和奇数+偶数会产生质数。然后呢,你发现这个多于一个的 1 都是无用的。你现在只保留一个 1,其他的 1 扔掉,然后把加和为质数的点连边,你就发现这构成了一张二分图(奇数点作为左部,偶数点作为右部),答案就是此图的最大独立集。
题解给的是拿网络流求这个最大独立集(家人们,S 组 T1 考网络流),但是其实匈牙利是可做的。你发现这个图有 \(n\) 个点,就很无脑的去想,最多有 \((\frac{n}{2})^2\) 条边,然后你的复杂度就是一个 \(\frac{n^3}{4}\),然后很妙啊,你的 \(T \le 4\)。然后 750 完全跑得过 \(n^3\)。
#include <bits/stdc++.h>
#define il inline
using namespace std;
const int bufsz = 1 << 20;
char ibuf[bufsz], *p1 = ibuf, *p2 = ibuf;
#define getchar() (p1 == p2 && (p2 = (p1 = ibuf) + fread(ibuf, 1, bufsz, stdin), p1 == p2) ? EOF : *p1++)
il int read() {
int x = 0; char ch = getchar(); bool t = 0;
while (ch < '0' || ch > '9') {t ^= ch == '-'; ch = getchar();}
while (ch >= '0' && ch <= '9') {x = (x << 1) + (x << 3) + (ch ^ 48); ch = getchar();}
return t ? -x : x;
}
const int N = 1500 + 10;
int n, a[N];
il int fpow(int a, int x, int MOD) {
int ans = 1;
while (x) {
if (x & 1) ans = 1ll * ans * a % MOD;
a = 1ll * a * a % MOD;
x >>= 1;
}
return ans;
}
int Test[20] = {2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37};
int test_time = 12;
bool isPrime(int p) {
if (p < 2) return 0;
int t = p - 1, k = 0;
while (!(t & 1)) {
t >>= 1;
k++;
}
for (int i = 0; i < test_time; i++) {
if (p == Test[i]) return 1;
int a = fpow(Test[i], t, p);
for (int j = 1; j <= k; j++) {
if (1ll * a * a % p == 1 && a != 1 && a != p - 1) {
return 0;
}
a = 1ll * a * a % p;
}
if (a != 1) return 0;
}
return 1;
}
vector<int> G[N];
int vis[N], pei[N];
il bool dfs(int x) {
for (int y : G[x]) {
if (vis[y]) continue;
vis[y] = 1;
if (!pei[y] || dfs(pei[y])) {
pei[y] = x;
return true;
}
}
return false;
}
int t[N];
int solve() {
n = read();
for (int i = 1; i <= n; i++) G[i].clear();
for (int i = 1; i <= n; i++) {
a[i] = read();
}
int cnt = 0;
for (int i = 1; i <= n; i++) t[i] = 0;
for (int i = 1; i <= n; i++) {
if (a[i] % 2 == 0) continue;
if (a[i] == 1) {
cnt++;
if (cnt > 1) {
t[i] = 1;
continue;
}
}
for (int j = 1; j <= n; j++) {
if (a[j] % 2 == 1) continue;
if (isPrime(a[i] + a[j])) {
G[i].push_back(j);
}
}
}
for (int i = 1; i <= n; i++) pei[i] = 0;
int ans = 0;
for (int i = 1; i <= n; i++) {
if (t[i] || a[i] % 2 == 0) continue;
for (int j = 1; j <= n; j++) vis[j] = 0;
if (dfs(i)) ans++;
}
printf("%d\n", n - max(0, cnt - 1) - ans);
return 0;
}
signed main() {
int qq = read();
while (qq--) {
solve();
}
return 0;
}
T2 爆炸
你一个炸弹如果是横向的被炸到那么他自己肯定是考虑纵向去炸,反之亦然。
去考虑对于每个炸弹和它上下左右的第一个炸弹连边。然后就会形成若干个连通块。考虑对每个连通块统计答案然后取其最大值即为答案。
不难发现,一个连通块要么是个环,要么是棵树。考虑如果是环怎么做。不是都说到这儿了你还不会?滚吧。你就 dfs 把他炸弹会炸到哪些行哪些列搞出来,然后一加,因为他会有相交的点会重复计算,暴力统计一下即可,复杂度均摊。那么对于树,无非是环删掉一条边,你就考虑在刚才那个玩意儿的基础上去删掉一行或一列即可。
这傻逼题考试时没写出来就是活该。我就是废物,这傻逼题有啥技术含量写不出来。
#include <bits/stdc++.h>
#define il inline
using namespace std;
const int INF = 0x3f3f3f3f;
const int N = 3000 + 10;
int n, m, kk, bb;
char S[N][N];
int a[N][N];
vector<int> G[N * N];
int tot, id[N][N], ii[N * N], jj[N * N];
il void link(int x, int y) {
G[x].push_back(y);
G[y].push_back(x);
}
int vis[N * N], fl;
vector<int> tmp;
il void dfs(int x, int fr) {
if (vis[x]) {
fl = 1;
return;
}
vis[x] = 1;
tmp.push_back(x);
for (int y : G[x]) {
if (y == fr) continue;
dfs(y, x);
}
}
int ti[N], tj[N], si[N], sj[N];
vector<int> idx, idy;
int main() {
ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
cin >> n >> m >> kk >> bb;
for (int i = 1; i <= n; i++) {
cin >> (S[i] + 1);
}
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
if (S[i][j] == '.') a[i][j] = 0;
if (S[i][j] == 'k') a[i][j] = 1;
if (S[i][j] == 'b') a[i][j] = 2;
if (a[i][j] == 1) {
si[i] += 1, sj[j] += 1;
}
}
}
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
if (a[i][j] != 2) continue;
id[i][j] = ++tot;
for (int k = i - 1; k >= 1; k--) {
if (a[k][j] == 2) {link(id[k][j], id[i][j]); break;}
}
for (int k = j - 1; k >= 1; k--) {
if (a[i][k] == 2) {link(id[i][k], id[i][j]); break;}
}
ii[id[i][j]] = i, jj[id[i][j]] = j;
}
}
int ans = 0;
for (int x = 1; x <= tot; x++) {
if (vis[x]) continue;
fl = 0;
tmp.clear(), idx.clear(), idy.clear();
dfs(x, 0);
for (int i = 1; i <= n; i++) ti[i] = 0;
for (int j = 1; j <= m; j++) tj[j] = 0;
for (int i : tmp) {
ti[ii[i]]++;
tj[jj[i]]++;
}
int sum = 0;
for (int i = 1; i <= n; i++) {
if (ti[i]) sum += si[i], idx.push_back(i);
}
for (int j = 1; j <= m; j++) {
if (tj[j]) sum += sj[j], idy.push_back(j);
}
for (int i : idx) {
for (int j : idy) {
if (a[i][j] == 1) {
si[i]--, sj[j]--;
sum--;
}
}
}
if (!fl) {
int mx = -INF;
for (int i = 1; i <= n; i++) {
if (ti[i] == 1) mx = max(mx, -si[i]);
}
for (int j = 1; j <= m; j++) {
if (tj[j] == 1) mx = max(mx, -sj[j]);
}
sum += mx;
}
ans = max(ans, sum);
}
cout << ans << "\n";
return 0;
}
T3 游戏
T4 公司
2025CSP-S模拟赛39
| T1 | T2 | T3 | T4 |
|---|---|---|---|
| 100 AC | 0 WA | 30 ME | 20 RE |
总分:150;排名:10/19。
A 了一个,后面部分分。
T1 **熊和青蛙
傻逼题,跑个 bfs 就完了,还傻逼写了半天 dfs。
#include <bits/stdc++.h>
#define il inline
using namespace std;
const int INF = 0x3f3f3f3f;
const int N = 180 + 5;
int n, m, kk, X, Y, Z;
char s[N][N][N];
int f[N][N][N], vis[N][N][N];
int dx[6] = {0, 0, 1, -1, 0, 0};
int dy[6] = {0, 0, 0, 0, 1, -1};
int dk[6] = {1, -1, 0, 0, 0, 0};
struct node {
int k, x, y;
};
queue<node> q;
int main() {
ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
cin >> n >> m >> kk >> X >> Y >> Z;
for (int k = 1; k <= kk; k++) {
for (int i = 1; i <= n; i++) cin >> (s[k][i] + 1);
}
if (Z == kk) {
cout << 0 << "\n";
return 0;
}
for (int k = 1; k <= kk; k++) {
for (int i = 1; i <= n; i++)
for (int j = 1; j <= m; j++) f[k][i][j] = INF;
}
f[Z][X][Y] = 0;
vis[Z][X][Y] = 1;
q.push({Z, X, Y});
while (!q.empty()) {
node t = q.front(); q.pop();
for (int i = 0; i < 6; i++) {
int xa = t.x + dx[i], ya = t.y + dy[i], ka = t.k + dk[i];
if (!vis[ka][xa][ya] && s[ka][xa][ya] == '.') {
f[ka][xa][ya] = f[t.k][t.x][t.y] + 1;
vis[ka][xa][ya] = 1;
q.push({ka, xa, ya});
}
}
}
int ans = INF;
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
ans = min(ans, f[kk][i][j]);
}
}
ans *= 2;
cout << ans << "\n";
return 0;
}
T2 数学题
T3 货物运输
首先你发现原题给的图是一个仙人掌,然后部分分给了树、环、基环树,那我们的思路就很明晰了。题目中的 \(s_i\) 下文统一称作 \(a_i\)。
树的做法
考虑每个点向他的父亲给一些(可以是负的)产生的贡献,设每点 \(i\) 要与他要转移的点的边权为 \(val_i\),点 \(i\) 子树的大小为 \(siz_i\),点权和为 \(sum_i\),设 \(V=\frac{\sum a_i}{n}\),令 \(b_i\) 表示每个点还需要多少,此处 \(b_i=sum_i-V\times siz_i\)。那么每个点的贡献即为:
就是一个类似拆贡献的思路。
环的做法
和上面其实大体思路类似。只不过此处没有父子关系,则 \(b_i=a_i-V\),最终要使 \(b_i\) 为 0。
不放设换上节点编号为 \(1,2,\dots,n\)。设 \(x_i\) 表示 \(i\) 给 \(i-1\) 的数量(为负数表示 \(i-1\) 给 \(i\))。则:
移项得:
规律显然,即 \(x_i=x_1-s_{i-1}\),其中 \(s_i=\sum_{j=1}^i a_j\)。那么 \(ans=\sum val_i |x_i|=\sum val_i |x_1-s_{i-1}|\)。
问题转化为求出 \(x_1\) 的值使得 \(\sum val_i |x_1-s_{i-1}|\) 最小。这东西看似是一坨,但是我只要告诉你这个初一的题是不是一下就会了。你把 \(val_i\) 个 \(s_{i-1}\) 总共 \(\sum val_i\) 个数拿到一起排个序,然后取中位数即可。
基环树的做法
显然,你把他看成环上挂了很多树,按照树的处理方式去做,把每个环上的点看作根节点即可。然后对于环上的点 \(i\),令 \(b_i=sum_i-V\times siz_i\),去跑一遍刚才环的做法即可。
仙人掌的做法
无非是把树的做法和环的做法拼接起来。你先去搞一个圆方树,然后正常取按照树的方式跑 dfs 并统计答案,遇到方点的话就把这个环一处理,其中 \(b_i\) 和上面的含义及表示相同。就没了。
#include <bits/stdc++.h>
#define il inline
#define int long long
using namespace std;
const int bufsz = 1 << 20;
char ibuf[bufsz], *p1 = ibuf, *p2 = ibuf;
#define getchar() (p1 == p2 && (p2 = (p1 = ibuf) + fread(ibuf, 1, bufsz, stdin), p1 == p2) ? EOF : *p1++)
il int read() {
int x = 0; char ch = getchar(); bool t = 0;
while (ch < '0' || ch > '9') {t ^= ch == '-'; ch = getchar();}
while (ch >= '0' && ch <= '9') {x = (x << 1) + (x << 3) + (ch ^ 48); ch = getchar();}
return t ? -x : x;
}
const int N = 2e5 + 10;
int n, m, a[N];
struct edge {
int y, w;
};
vector<edge> G[N];
unordered_map<int, unordered_map<int, int>> mp;
int st1[N], head1, dfn[N], low[N], cnt, tim;
vector<int> dcc[N];
il void tarjan(int x, int fa) {
low[x] = dfn[x] = ++tim;
st1[++head1] = x;
if (fa == 0 && G[x].size() == 0) {
dcc[++cnt].push_back(x);
return;
}
for (edge i : G[x]) {
int y = i.y;
if (dfn[y] == 0) {
tarjan(y, x);
low[x] = min(low[x], low[y]);
if (low[y] >= dfn[x]) {
cnt++;
while (true) {
int z = st1[head1--];
dcc[cnt].push_back(z);
if (y == z) break;
}
dcc[cnt].push_back(x);
}
} else if (y != fa) {
low[x] = min(low[x], dfn[y]);
}
}
}
int id[N], tot;
int S, V, ans;
vector<edge> E[N];
int siz[N], sum[N], w[N];
int b[N];
il int solve2();
il void dfs(int x, int fa) {
siz[x] = (x <= n);
sum[x] = a[x];
for (edge i : E[x]) {
if (i.y == fa) continue;
w[i.y] = i.w;
dfs(i.y, x);
siz[x] += siz[i.y];
sum[x] += sum[i.y];
}
b[x] = sum[x] - V * siz[x];
if (x <= n) ans += w[x] * abs(b[x]);
if (x > n) {
tot = 0;
for (int i : dcc[x - n]) id[++tot] = i;
solve2();
}
}
int s[N], val[N];
struct node {
int s, val;
il bool operator < (const node & cmp) const {
return s < cmp.s;
}
} tt[N];
il int solve2() {
val[id[1]] = mp[id[1]][id[tot]];
for (int i = 2; i <= tot; i++) {
val[id[i]] = mp[id[i]][id[i - 1]];
}
int tmp = 0;
for (int i = 1; i <= tot; i++) {
s[i] = s[i - 1] + b[id[i]];
tmp += val[id[i]];
tt[i] = {s[i - 1], val[id[i]]};
}
tmp = tmp / 2 + 1;
int x = s[1];
sort(tt + 1, tt + 1 + tot);
for (int i = 1; i <= tot; i++) {
tmp -= tt[i].val;
if (tmp <= 0) {
x = tt[i].s;
break;
}
}
for (int i = 1; i <= tot; i++) {
ans += val[id[i]] * abs(x - s[i - 1]);
}
return 0;
}
signed main() {
n = read(), m = read();
for (int i = 1; i <= n; i++) a[i] = read();
for (int i = 1; i <= m; i++) {
int x = read(), y = read(), w = read();
G[x].push_back({y, w});
G[y].push_back({x, w});
mp[x][y] = mp[y][x] = w;
}
for (int i = 1; i <= n; i++) S += a[i];
V = S / n;
for (int i = 1; i <= n; i++) {
if (!dfn[i]) tarjan(i, 0);
}
for (int i = 1; i <= cnt; i++) {
if (dcc[i].size() == 2) {
int x = dcc[i][0], y = dcc[i][1], w = mp[x][y];
E[x].push_back({y, w});
E[y].push_back({x, w});
continue;
}
for (int j : dcc[i]) {
E[j].push_back({n + i, 0});
E[n + i].push_back({j, 0});
}
}
dfs(1, 0);
printf("%lld\n", ans);
return 0;
}
T4 士兵游戏
2025CSP-S模拟赛41
| T1 | T2 | T3 | T4 |
|---|---|---|---|
| 20 WA | 27 WA | 70 TLE | - |
总分:107;排名:12/19。
T1 写假了,T2 写假了,T3 暴力,T4 忘打了。
T1 限速(speed)
比较智障。
就是你跑一遍 DSU,然后如果有 \(\ge k\) 的边直接统计贡献,否则把目前最大的边搞成 \(k\) 即可。自己手玩一下就能发现是对的。
#include <bits/stdc++.h>
#define il inline
#define int long long
using namespace std;
const int bufsz = 1 << 20;
char ibuf[bufsz], *p1 = ibuf, *p2 = ibuf;
#define getchar() (p1 == p2 && (p2 = (p1 = ibuf) + fread(ibuf, 1, bufsz, stdin), p1 == p2) ? EOF : *p1++)
il int read() {
int x = 0; char ch = getchar(); bool t = 0;
while (ch < '0' || ch > '9') {t ^= ch == '-'; ch = getchar();}
while (ch >= '0' && ch <= '9') {x = (x << 1) + (x << 3) + (ch ^ 48); ch = getchar();}
return t ? -x : x;
}
const int INF = 0x3f3f3f3f3f3f3f3f;
const int N = 2e5 + 10;
int n, m, kk;
struct node {
int x, y, w;
il bool operator < (const node & cmp) const {
return w < cmp.w;
}
} e[N];
int fa[N];
il int getfa(int x) {
return x == fa[x] ? x : fa[x] = getfa(fa[x]);
}
signed main() {
freopen("speed.in", "r", stdin);
freopen("speed.out", "w", stdout);
n = read(), m = read(), kk = read();
for (int i = 1; i <= m; i++) {
int x = read(), y = read(), w = read();
e[i] = {x, y, w};
}
sort(e + 1, e + 1 + m);
for (int i = 1; i <= n; i++) fa[i] = i;
int ans = 0, fl = 0, mn = INF;
for (int i = 1; i <= m; i++) {
int x = getfa(e[i].x), y = getfa(e[i].y);
if (x != y) {
fa[y] = x;
if (e[i].w > kk) ans += e[i].w - kk, fl = 1;
}
mn = min(mn, abs(kk - e[i].w));
}
if (!fl) ans = mn;
printf("%lld\n", ans);
return 0;
}
T2 酒鬼(drunkard)
考试时没考虑到 \(p_i=1\) 的情况,然后 max 写假了,就没分了。
首先对于 \(p_i > 1\) 的情况是显然的,min 只能是 0 或 1,max 显然,随便搞一下即可。
然后你考虑有 \(p_i=1\) 的情况,可能这个人在点 1 待好久才走,就是他可能前面很多的 \(p_i=1\) 的线索都是不满足奇偶性的,然后就是加一堆特判去搞他就完了。
他妈的这种全是 if 的代码的题出出来纯恶心人,真他妈的傻逼。
#include <bits/stdc++.h>
#define il inline
using namespace std;
const int INF = 0x3f3f3f3f;
const int N = 2e5 + 10;
int n, m;
char op[5];
struct node {
int p, q;
il bool operator < (const node & cmp) const {
return q < cmp.q;
}
};
set<node> st;
int fl1 = 0;
il bool check(node a, node b) {
if (a.p == 1 && a.q <= fl1) a.q = fl1;
if (a.p > 1 && a.q <= fl1) return true;
if (b.p > 1 && b.q <= fl1) return true;
if (b.q - a.q < abs(a.p - b.p) || (b.q - a.q) % 2 != abs(a.p - b.p) % 2) {
if (a.p == 1 && b.p == 1) {
if (a.q <= fl1 && fl1 < b.q) fl1++;
else if (a.q >= fl1 && fl1 < b.q) fl1 = a.q + 1;
else fl1 = max(fl1, b.q);
return false;
}
if (a.p == 1 && b.q > fl1) {
fl1 = max(fl1, a.q + ((b.q - a.q) % 2 != abs(a.p - b.p) % 2));
return false;
}
return true;
}
return false;
}
int main() {
freopen("drunkard.in", "r", stdin);
freopen("drunkard.out", "w", stdout);
ios::sync_with_stdio(0); cin.tie(0), cout.tie(0);
cin >> n >> m;
int fl = 0;
set<node>::iterator it, it1, it2;
int pos = INF, tim = INF;
while (m--) {
cin >> op;
if (op[0] == 'c') {
int p, q;
cin >> p >> q;
if (p > q + 1) {
fl = 1;
continue;
}
node tmp = {p, q};
it = st.find(tmp);
if (it != st.end()) {
if ((*it).p != p) fl = 1;
continue;
}
st.insert(tmp);
it1 = it2 = it = st.find(tmp);
if (it != st.begin()) fl |= check(*(--it1), *it);
if (++it2 != st.end()) fl |= (check(*it, *it2));
if (p > 1 && q < tim) {
pos = p;
tim = q;
}
} else if (op[1] == 'i') {
if (fl) {
cout << "bad\n";
continue;
}
if (st.empty()) {
cout << 0 << "\n";
} else {
cout << max(((*st.begin()).q - (*st.begin()).p + 1) % 2, fl1) << "\n";
}
} else {
if (fl) {
cout << "bad\n";
continue;
}
if (pos == INF) {
cout << "inf\n";
continue;
}
cout << tim - pos + 1 << "\n";
}
}
return 0;
}
T3 距离(distance)
T4 团队选拔(selection)
2025CSP-S模拟赛42
| T1 | T2 | T3 | T4 |
|---|---|---|---|
| 100 AC | 40 RE | 0 WA | 0 TLE |
总分:40;排名:13/20。
T1 签,T2 是暴力,后面不会。
T1 追逐游戏
签到题,随意做。
#include <bits/stdc++.h>
#define il inline
using namespace std;
const int bufsz = 1 << 20;
char ibuf[bufsz], *p1 = ibuf, *p2 = ibuf;
#define getchar() (p1 == p2 && (p2 = (p1 = ibuf) + fread(ibuf, 1, bufsz, stdin), p1 == p2) ? EOF : *p1++)
il int read() {
int x = 0; char ch = getchar(); bool t = 0;
while (ch < '0' || ch > '9') {t ^= ch == '-'; ch = getchar();}
while (ch >= '0' && ch <= '9') {x = (x << 1) + (x << 3) + (ch ^ 48); ch = getchar();}
return t ? -x : x;
}
bool Beg;
const int N = 2e5 + 10;
int n, qq;
vector<int> G[N];
int fa[N][22], dep[N];
il void dfs(int x, int father) {
fa[x][0] = father;
dep[x] = dep[father] + 1;
for (int i = 1; i <= 19; i++) {
fa[x][i] = fa[fa[x][i - 1]][i - 1];
}
for (int y : G[x]) {
if (y == father) continue;
dfs(y, x);
}
}
il int getlca(int x, int y) {
if (dep[x] < dep[y]) swap(x, y);
for (int i = 19; i >= 0; i--) {
if (dep[fa[x][i]] >= dep[y]) x = fa[x][i];
}
if (x == y) return x;
for (int i = 19; i >= 0; i--) {
if (fa[x][i] != fa[y][i]) {
x = fa[x][i], y = fa[y][i];
}
}
return fa[x][0];
}
il int getdis(int x, int y) {
return dep[x] + dep[y] - 2 * dep[getlca(x, y)];
}
il int kfa(int x, int k) {
for (int i = 0; i <= 19; i++) {
if ((k >> i) & 1) x = fa[x][i];
}
return x;
}
bool End;
il void Used() { cerr << "Use: " << (&Beg - &End) / 1024.0 / 1024.0 << "MB " << (double)clock() * 1000.0 / CLOCKS_PER_SEC << "ms\n";}
int main() {
freopen("chase.in", "r", stdin);
freopen("chase.out", "w", stdout);
n = read(), qq = read();
for (int i = 1; i < n; i++) {
int x = read(), y = read();
G[x].push_back(y);
G[y].push_back(x);
}
dfs(1, 0);
while (qq--) {
int s = read(), t = read(), u = read();
int l = getlca(s, t);
int p = getlca(l, u);
if (p == l) {
p = getlca(u, s);
if (p == l) {
p = getlca(u, t);
}
} else {
p = l;
}
int len1 = getdis(s, p), len2 = getdis(u, p);
if (len1 < len2) {
printf("%d %d\n", getdis(u, t), t);
} else {
int lca = getlca(s, u);
int len = (len1 + len2) / 2;
if (len <= getdis(u, lca)) {
int met = kfa(u, len);
printf("%d %d\n", getdis(met, s), met);
} else {
int met = kfa(s, len1 + len2 - len);
printf("%d %d\n", getdis(met, s), met);
}
}
}
Used();
return 0;
}
T2 统计
T3 软件工程
T4 命运的X
2025CSP-S模拟赛43
| T1 | T2 | T3 | T4 |
|---|---|---|---|
| 30 TLE | 0 RE | 0 WA | 0 TLE |
总分:30; 排名:16/19。
考的比较唐氏。具体的写到总结里了。
T1 四舍五入
T2 填算符
T3 道路修建
T4 逆序图
2025CSP-S模拟赛44 比赛总结
咕。。
啥都不写是不是不好啊。。
放一张世子的新皮肤吧。

作为暑假最后一场模拟赛(虽然是好几天前打的),但是虽然不写总结但是挂上,予以尊重。
暑假集训总结的头图用的是世子的旧皮肤,这里这张世子的新皮肤也算是一个呼应吧。

浙公网安备 33010602011771号