2025 CSP-S 模拟赛 7
2025 CSP-S 模拟赛 7
得分
| T1 | T2 | T3 | T4 | 得分 | 排名 |
|---|---|---|---|---|---|
| \(80\) | \(100\) | \(10\) | \(24\) | \(214\) | \(1/18\) |
题解
T1 Lesson5!
首先路径起点终点不确定,那么我们先建立超源超汇 \(S,T\),求出最长路后减 \(2\) 即可。考虑怎样求出不包含某个点的最长路,我们先从 \(S,T\) 出发向每个点跑一遍最短路,这样的话就可以求出经过某一条边的最长路长度。接下来我们动态维护两个点集 \(X,Y\),表示已经考虑过的点集和还没有考虑的点集,初始时 \(Y=U\)。
接下来我们按照拓扑序向 \(X\) 中加点,假如我们要加入 \(i\),那么此时拓扑序比 \(i\) 大的点必然不能走,能走的只有拓扑序比 \(i\) 小的。我们此时维护所有 \(X\to Y\) 的边,那么如果把所有指向 \(i\) 的边删去,剩下的边的最大值就是当前最长路。
那么我们用数据结构维护一下这个最大值即可,可以用 multiset / 可删堆 / 权值线段树实现,复杂度 \(O(n\log n)\)。
#include <bits/stdc++.h>
#define il inline
using namespace std;
const int Maxn = 1e5 + 5, Maxm = 1e6 + 5;
const int Inf = 2e9;
template <typename T> il void chkmax(T &x, T y) {x = (x >= y ? x : y);}
template <typename T> il void chkmin(T &x, T y) {x = (x <= y ? x : y);}
template <typename T>
il void read(T &x) {
x = 0; char ch = getchar(); bool flg = 0;
for(; ch < '0' || ch > '9'; ch = getchar()) flg = (ch == '-');
for(; ch >= '0' && ch <= '9'; ch = getchar()) x = (x << 1) + (x << 3) + (ch ^ 48);
flg ? x = -x : 0;
}
template <typename T>
il void write(T x, bool typ = 1) {
static short Stk[50], Top = 0;
x < 0 ? putchar('-'), x = -x : 0;
do Stk[++Top] = x % 10, x /= 10; while(x);
while(Top) putchar(Stk[Top--] | 48);
typ ? putchar('\n') : putchar(' ');
}
il void IOS() {ios::sync_with_stdio(0); cin.tie(0), cout.tie(0);}
il void File() {freopen("johnny9.in", "r", stdin);}
bool Beg;
int T;
int n, m, s, t;
struct Edge {
int u, v, w;
}e[Maxm];
struct Graph {
int head[Maxn], edgenum;
struct node {
int nxt, to, id;
}edge[Maxm];
il void add(int u, int v, int id) {
edge[++edgenum] = {head[u], v, id}; head[u] = edgenum;
}
}G1, G2;
int deg[Maxn];
il void add(int u, int v, int id) {
deg[v]++;
G1.add(u, v, id), G2.add(v, u, id);
}
queue <int> q;
int ord[Maxn], cnt;
il void topo() {
cnt = 0; q.push(0);
while(!q.empty()) {
int x = q.front(); q.pop();
ord[++cnt] = x;
for(int i = G1.head[x]; i; i = G1.edge[i].nxt) {
int to = G1.edge[i].to;
if(!(--deg[to])) q.push(to);
}
}
}
namespace SGT {
struct node {
int cnt, mx;
}t[Maxm << 1];
#define ls(p) (p << 1)
#define rs(p) (p << 1 | 1)
il void pushup(int p) {
t[p].mx = max(t[ls(p)].mx, t[rs(p)].mx);
}
il void build(int p, int l, int r) {
t[p].cnt = t[p].mx = 0;
if(l == r) return ;
int mid = (l + r) >> 1;
build(ls(p), l, mid), build(rs(p), mid + 1, r);
pushup(p);
}
il void mdf(int p, int l, int r, int x, int val) {
if(l == r) {
t[p].cnt += val;
if(t[p].cnt) t[p].mx = l;
else t[p].mx = 0;
return ;
}
int mid = (l + r) >> 1;
if(x <= mid) mdf(ls(p), l, mid, x, val);
else mdf(rs(p), mid + 1, r, x, val);
pushup(p);
}
int query() {
return t[1].mx;
}
}
int pre[Maxn], suf[Maxn];
il void solve() {
G1.edgenum = G2.edgenum = 0;
for(int i = 0; i <= n + 1; i++) G1.head[i] = G2.head[i] = 0;
read(n), read(m);
if(n == 1) {cout << "1 0\n"; return ;}
for(int i = 1; i <= m; i++) {
read(e[i].u), read(e[i].v);
add(e[i].u, e[i].v, i);
}
s = 0, t = n + 1;
for(int i = 1; i <= n; i++) e[++m] = {s, i, 0}, add(s, i, m);
for(int i = 1; i <= n; i++) e[++m] = {i, t, 0}, add(i, t, m);
topo();
for(int i = 1; i <= cnt; i++) {
int x = ord[i];
pre[x] = 0;
for(int i = G2.head[x]; i; i = G2.edge[i].nxt) {
int to = G2.edge[i].to;
chkmax(pre[x], pre[to] + 1);
}
}
for(int i = cnt; i >= 1; i--) {
int x = ord[i];
suf[x] = 0;
for(int i = G1.head[x]; i; i = G1.edge[i].nxt) {
int to = G1.edge[i].to;
chkmax(suf[x], suf[to] + 1);
}
}
for(int i = 1; i <= m; i++) e[i].w = pre[e[i].u] + suf[e[i].v] - 1;
int ans1 = 0, ans2 = Inf;
SGT::build(1, 1, 5e5 + 5);
for(int i = 1; i <= cnt - 1; i++) {
int x = ord[i];
for(int i = G2.head[x]; i; i = G2.edge[i].nxt) {
int id = G2.edge[i].id;
SGT::mdf(1, 1, 5e5 + 5, e[id].w, -1);
}
int v = SGT::query();
if(v) {
if(v < ans2) ans1 = x, ans2 = v;
else if(v == ans2) chkmin(ans1, x);
}
for(int i = G1.head[x]; i; i = G1.edge[i].nxt) {
int id = G1.edge[i].id;
SGT::mdf(1, 1, 5e5 + 5, e[id].w, 1);
}
}
write(ans1, 0), write(ans2);
}
bool End;
il void Usd() {cerr << (&Beg - &End) / 1024.0 / 1024.0 << "MB " << (double)clock() * 1000.0 / CLOCKS_PER_SEC << "ms\n"; }
int main() {
read(T);
while(T--) solve();
Usd();
return 0;
}
T2 贝尔数
首先发现 \(95041567=31\times 37\times 41\times 43\times 47\),所以我们可以先求出模这些质数的答案然后再用 CRT 合并。求质数的答案题目中给出了一个式子,显然这是关于相邻 \(p\) 项的一个转移。用一个 \(p\times p\) 的矩阵加速转移即可,复杂度 \(O(p^3\log n)\)。
考场上比较降智,用的是分块实现矩阵转移,复杂度是 \(O(p\sqrt{np})\) 的,非常搞笑。
#include <bits/stdc++.h>
#define il inline
using namespace std;
const int Maxn = 5e5 + 5;
const int Inf = 2e9;
const int M = 95041567;
int Mod;
il int Add(int x, int y) {return x + y >= Mod ? x + y - Mod: x + y;} il void pls(int &x, int y) {x = Add(x, y);}
il int Del(int x, int y) {return x - y < 0 ? x - y + Mod : x - y;} il void sub(int &x, int y) {x = Del(x, y);}
il int qpow(int a, int b, int P = Mod) {int res = 1; for(; b; a = 1ll * a * a % P, b >>= 1) if(b & 1) res = 1ll * res * a % P; return res;}
il int Inv(int a) {return qpow(a, Mod - 2);}
template <typename T> il void chkmax(T &x, T y) {x = (x >= y ? x : y);}
template <typename T> il void chkmin(T &x, T y) {x = (x <= y ? x : y);}
template <typename T>
il void read(T &x) {
x = 0; char ch = getchar(); bool flg = 0;
for(; ch < '0' || ch > '9'; ch = getchar()) flg = (ch == '-');
for(; ch >= '0' && ch <= '9'; ch = getchar()) x = (x << 1) + (x << 3) + (ch ^ 48);
flg ? x = -x : 0;
}
template <typename T>
il void write(T x, bool typ = 1) {
static short Stk[50], Top = 0;
x < 0 ? putchar('-'), x = -x : 0;
do Stk[++Top] = x % 10, x /= 10; while(x);
while(Top) putchar(Stk[Top--] | 48);
typ ? putchar('\n') : putchar(' ');
}
il void File() {freopen("in.txt", "r", stdin); freopen("out1.txt", "w", stdout);}
bool Beg;
int T, n;
int B[6][51], C[51][51];
int c[Maxn][50], a[51], t[51];
void init(int x, int p) {
Mod = p;
C[0][0] = 1;
for(int i = 1; i <= p; i++) {
C[i][0] = 1;
for(int j = 1; j <= p; j++) C[i][j] = Add(C[i - 1][j], C[i - 1][j - 1]);
}
B[x][0] = 1;
for(int i = 1; i <= p; i++) {
B[x][i] = 0;
for(int j = 0; j < i; j++) pls(B[x][i], 1ll * B[x][j] * C[i - 1][j] % Mod);
}
}
int work(int n, int x, int p) {
Mod = p;
int L = sqrt(n / p) + 1;
for(int i = 0; i < p; i++) {
for(int j = 0; j < p; j++) c[i][j] = 0;
c[i][i] = 1;
}
for(int i = p; i <= (L + 1) * p - 1; i++) {
for(int j = 0; j < p; j++) c[i][j] = Add(c[i - p][j], c[i - p + 1][j]);
}
for(int i = 0; i < p; i++) a[i] = B[x][i];
int k = (n / p) / L;
for(int i = 1; i <= k; i++) {
for(int j = 0; j < p; j++) {
t[j] = 0;
for(int r = 0; r < p; r++) pls(t[j], 1ll * a[r] * c[L * p + j][r] % Mod);
}
for(int j = 0; j < p; j++) a[j] = t[j];
}
int bg = k * L * p, cha = n - bg, res = 0;
for(int i = 0; i < p; i++) pls(res, 1ll * c[cha][i] * a[i] % Mod);
return res;
}
int p[] = {0, 31, 37, 41, 43, 47};
void solve() {
read(n);
int res = 0;
for(int i = 1; i <= 5; i++) {
int c = M / p[i], invc = qpow(c, p[i] - 2, p[i]);
int r = work(n, i, p[i]);
(res += 1ll * r * c % M * invc % M) %= M;
}
write(res);
}
bool End;
il void Usd() {cerr << (&Beg - &End) / 1024.0 / 1024.0 << "MB " << (double)clock() * 1000.0 / CLOCKS_PER_SEC << "ms\n"; }
int main() {
read(T);
for(int i = 1; i <= 5; i++) init(i, p[i]);
while(T--) solve();
Usd();
return 0;
}
T3 穿越广场
这道题考场上没有人 A,有一半的人是因为这道题在 T3,还有一半的人是因为 \(n,m\) 读反了……
实际上是一道 AC 自动机上 dp 的板子,设 \(dp(i,j,k,S)\) 表示当前用了 \(i\) 个 D,\(j\) 个 R,走到 AC 自动机上第 \(k\) 个节点,已经匹配好的字符串集合为 \(S\) 的方案数。转移直接暴力枚举即可,复杂度 \(O(nm|S|)\)。
#include <bits/stdc++.h>
#define il inline
using namespace std;
const int Maxn = 101;
const int Inf = 2e9;
const int Mod = 1e9 + 7;
il int Add(int x, int y) {return x + y >= Mod ? x + y - Mod: x + y;} il void pls(int &x, int y) {x = Add(x, y);}
il int Del(int x, int y) {return x - y < 0 ? x - y + Mod : x - y;} il void sub(int &x, int y) {x = Del(x, y);}
il int qpow(int a, int b, int P = Mod) {int res = 1; for(; b; a = 1ll * a * a % P, b >>= 1) if(b & 1) res = 1ll * res * a % P; return res;}
il int Inv(int a) {return qpow(a, Mod - 2);}
template <typename T> il void chkmax(T &x, T y) {x = (x >= y ? x : y);}
template <typename T> il void chkmin(T &x, T y) {x = (x <= y ? x : y);}
il void IOS() {ios::sync_with_stdio(0); cin.tie(0), cout.tie(0);}
bool Beg;
int T;
int m, n;
string s, t;
struct ACAM {
int son[2], fail, tag;
}tr[501];
int tot = 1;
int trans(char ch) {
if(ch == 'D') return 0;
else return 1;
}
void insert(string s, int x) {
int u = 1;
for(int i = 0; i < s.size(); i++) {
int ch = trans(s[i]);
if(!tr[u].son[ch]) tr[u].son[ch] = ++tot;
u = tr[u].son[ch];
}
tr[u].tag |= (1 << x);
}
queue <int> q;
void build() {
tr[0].son[0] = tr[0].son[1] = 1;
q.push(1); tr[1].fail = 0;
while(!q.empty()) {
int u = q.front(); q.pop();
for(int i = 0; i <= 1; i++) {
int v = tr[u].son[i], fa = tr[u].fail;
tr[u].tag |= tr[fa].tag;
if(!v) {tr[u].son[i] = tr[fa].son[i]; continue;}
tr[v].fail = tr[fa].son[i];
q.push(v);
}
}
}
int dp[101][101][501][4];
void solve() {
for(int i = 1; i <= tot; i++) tr[i] = {0, 0, 0, 0};
tot = 1;
cin >> m >> n >> s >> t;
insert(s, 0), insert(t, 1);
build();
for(int i = 0; i <= n; i++) for(int j = 0; j <= m; j++) for(int k = 1; k <= tot; k++) for(int S = 0; S < 4; S++) dp[i][j][k][S] = 0;
dp[0][0][1][0] = 1;
for(int i = 0; i <= n; i++) {
for(int j = 0; j <= m; j++) {
for(int k = 1; k <= tot; k++) {
for(int S = 0; S < 4; S++) {
if(i != n) pls(dp[i + 1][j][tr[k].son[0]][S | tr[tr[k].son[0]].tag], dp[i][j][k][S]);
if(j != m) pls(dp[i][j + 1][tr[k].son[1]][S | tr[tr[k].son[1]].tag], dp[i][j][k][S]);
}
}
}
}
int ans = 0;
for(int k = 1; k <= tot; k++) {
pls(ans, dp[n][m][k][3]);
}
cout << ans << '\n';
}
bool End;
il void Usd() {cerr << (&Beg - &End) / 1024.0 / 1024.0 << "MB " << (double)clock() * 1000.0 / CLOCKS_PER_SEC << "ms\n"; }
int main() {
IOS();
cin >> T;
while(T--) solve();
Usd();
return 0;
}
T4 欢乐豆
首先观察到修改的边非常少,所以肯定要从这里入手。我们把所有修改过的点全部拿出来,然后会形成若干连通块,显然这些点点数不超过 \(2m\)。此时不在这些连通块内的点我们已经可以直接求了,显然就是 \((n-1)a_x\)。
然后考虑求解一个连通块内的点的答案,先枚举起点,然后我们分为两部分考虑,即块内到块内块内到块外。
-
块内到块内:
我们直接做一遍 dijkstra 跑出当前点到其他点的最短路。但是此时的问题是这个连通块依然是一个完全图(因为我们还是有可能走原边权 \(a_x\) 的),直接跑最短路显然不可行。
考虑到连通块内真正有用的边只有 \(O(m)\) 条,剩下的边有很多重复,所以我们考虑用线段树优化松弛的过程。那么此时我们需要的操作只有找到最小值和对应位置、单点 \(\text{chkmin}\) 和区间 \(\text{chkmin}\),显然都可以简单实现。当然需要注意的是取出最小值后需要打上 \(vis\) 标记,以后不能再松弛该点。
不过块内到块内还有一种情况就是先拐到块外再回来,那么此时拐出去的点一定是 \(a\) 最小的那个。同时,我们一定是从 \(dis_y+a_y\) 最小的点 \(y\) 拐出去的。所以用 \(\min\{dis_y+a_y\}+a_{mn}\) 更新一遍所有 \(dis\) 即可。
-
块内到块外:
和上面类似的,我们一定是先走到连通块边界再拐出去,那么此时的路径权值直接就是 \(\min\{dis_y+a_y\}\),乘上连通块外点数即可。
综上我们就解决了连通块内的求解,复杂度是 \(O(m^2\log m)\) 的,可以通过。
#include <bits/stdc++.h>
#define il inline
#define int long long
using namespace std;
const int Maxn = 2e5 + 5, Maxm = 5e3 + 5;
const int Inf = 1e18;
template <typename T> il void chkmax(T &x, T y) {x = (x >= y ? x : y);}
template <typename T> il void chkmin(T &x, T y) {x = (x <= y ? x : y);}
template <typename T>
il void read(T &x) {
x = 0; char ch = getchar(); bool flg = 0;
for(; ch < '0' || ch > '9'; ch = getchar()) flg = (ch == '-');
for(; ch >= '0' && ch <= '9'; ch = getchar()) x = (x << 1) + (x << 3) + (ch ^ 48);
flg ? x = -x : 0;
}
template <typename T>
il void write(T x, bool typ = 1) {
static short Stk[50], Top = 0;
x < 0 ? putchar('-'), x = -x : 0;
do Stk[++Top] = x % 10, x /= 10; while(x);
while(Top) putchar(Stk[Top--] | 48);
typ ? putchar('\n') : putchar(' ');
}
il void File() {freopen("sample_happybean4.in", "r", stdin);}
bool Beg;
int n, m, a[Maxn];
#define pii pair<int, int>
#define mk make_pair
vector <pii> E[Maxn];
int vis[Maxn];
il void add(int u, int v, int w) {E[u].push_back(mk(v, w));}
namespace DSU {
int fa[Maxn], siz[Maxn];
void init() {for(int i = 1; i <= n; i++) fa[i] = i, siz[i] = 1;}
int find(int x) {return fa[x] == x ? x : fa[x] = find(fa[x]);}
void merge(int x, int y) {
x = find(x), y = find(y);
if(x == y) return ;
if(siz[x] > siz[y]) swap(x, y);
siz[y] += siz[x], fa[x] = y;
}
}
int id[Maxn], cnt;
vector <int> pts[Maxm];
int ans;
namespace SGT {
struct node {
int mn, pos, tag, vis;
}t[Maxm << 2];
#define ls(p) (p << 1)
#define rs(p) (p << 1 | 1)
il void pushup(int p) {
t[p].mn = min(t[ls(p)].mn, t[rs(p)].mn);
if(t[p].mn == t[ls(p)].mn && !t[ls(p)].vis) t[p].pos = t[ls(p)].pos;
else t[p].pos = t[rs(p)].pos;
t[p].vis = t[ls(p)].vis & t[rs(p)].vis;
}
il void build(int p, int l, int r) {
t[p].mn = Inf, t[p].pos = l, t[p].tag = Inf, t[p].vis = 0;
if(l == r) return ;
int mid = (l + r) >> 1;
build(ls(p), l, mid), build(rs(p), mid + 1, r);
}
il void pushtag(int p, int v) {chkmin(t[p].mn, v), chkmin(t[p].tag, v);}
il void pushdown(int p) {
if(t[p].tag == Inf) return ;
if(!t[ls(p)].vis) pushtag(ls(p), t[p].tag);
if(!t[rs(p)].vis) pushtag(rs(p), t[p].tag);
t[p].tag = Inf;
}
il void mdf(int p, int l, int r, int x, int val) {
if(l == r) {
if(!t[p].vis) chkmin(t[p].mn, val);
return ;
}
pushdown(p);
int mid = (l + r) >> 1;
if(x <= mid) mdf(ls(p), l, mid, x, val);
else mdf(rs(p), mid + 1, r, x, val);
pushup(p);
}
il void mdf(int p, int l, int r, int pl, int pr, int val) {
if(pl > pr || t[p].vis) return ;
if(pl <= l && r <= pr) {
pushtag(p, val);
return ;
}
pushdown(p);
int mid = (l + r) >> 1;
if(pl <= mid) mdf(ls(p), l, mid, pl, pr, val);
if(pr > mid) mdf(rs(p), mid + 1, r, pl, pr, val);
pushup(p);
}
il void cov(int p, int l, int r, int x) {
if(l == r) {
t[p].vis = 1; t[p].mn = Inf;
return ;
}
pushdown(p);
int mid = (l + r) >> 1;
if(x <= mid) cov(ls(p), l, mid, x);
else cov(rs(p), mid + 1, r, x);
pushup(p);
}
}
multiset <int> S;
int minn;
int num[Maxn], poi[Maxn], dis[Maxn];
il void dijkstra(int x, int m) {
SGT::build(1, 1, m);
SGT::mdf(1, 1, m, num[x], 0);
while(SGT::t[1].mn != Inf) {
int x = SGT::t[1].pos, ds = SGT::t[1].mn;
SGT::cov(1, 1, m, x); x = poi[x];
dis[x] = ds;
int lst = 1;
for(auto To : E[x]) {
int to = To.first, w = To.second;
SGT::mdf(1, 1, m, num[to], ds + w);
SGT::mdf(1, 1, m, lst, num[to] - 1, ds + a[x]);
lst = num[to] + 1;
}
SGT::mdf(1, 1, m, lst, m, ds + a[x]);
}
int mn = Inf;
for(int i = 1; i <= m; i++) chkmin(mn, dis[poi[i]] + a[poi[i]]);
for(int i = 1; i <= m; i++) ans += min(dis[poi[i]], mn + minn);
ans += (n - m) * mn;
}
bool End;
il void Usd() {cerr << (&Beg - &End) / 1024.0 / 1024.0 << "MB " << (double)clock() * 1000.0 / CLOCKS_PER_SEC << "ms\n"; }
signed main() {
read(n), read(m);
for(int i = 1; i <= n; i++) read(a[i]), S.insert(a[i]);
DSU::init();
for(int i = 1, u, v, w; i <= m; i++) {
read(u), read(v), read(w);
add(u, v, w); vis[u] = vis[v] = 1;
DSU::merge(u, v);
}
for(int i = 1; i <= n; i++) {
sort(E[i].begin(), E[i].end());
if(vis[i] && DSU::find(i) == i) id[i] = ++cnt;
}
for(int i = 1; i <= n; i++) {
if(vis[i]) pts[id[DSU::find(i)]].push_back(i);
else ans += (n - 1) * a[i];
}
for(int i = 1; i <= cnt; i++) {
sort(pts[i].begin(), pts[i].end());
for(int j = 0; j < pts[i].size(); j++) {
num[pts[i][j]] = j + 1, poi[j + 1] = pts[i][j];
S.erase(S.find(a[pts[i][j]]));
}
minn = S.size() ? *S.begin() : Inf;
for(auto x : pts[i]) dijkstra(x, pts[i].size());
for(int j = 0; j < pts[i].size(); j++) S.insert(a[pts[i][j]]);
}
write(ans);
Usd();
return 0;
}

浙公网安备 33010602011771号