2025 CSP-S 模拟赛 7

2025 CSP-S 模拟赛 7

\(\text{Link}\)

得分

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;
}
posted @ 2025-05-05 08:33  UKE_Automation  阅读(271)  评论(0)    收藏  举报