2025 CSP-S 模拟赛 11

2025 CSP-S 模拟赛 11

\(\text{Link}\)

得分

T1 T2 T3 T4 Sum Rank
\(100\) \(100\) \(16\) \(0\) \(216\) \(1/20\)

题解

T1 异或

区域加法问题肯定考虑差分思想,我们将每一次修改差分成这样:

1
1  x
1  x  x 
x -1 -1 -1

然后斜着做一遍前缀和即可得出最后答案。再利用普通的矩阵加法实现这个操作即可。

#include <bits/stdc++.h>
#define il inline
#define int long long

using namespace std;

const int Maxn = 1e3 + 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("in.txt", "r", stdin); freopen("out.txt", "w", stdout);}
bool Beg;

int n, q;
int a[Maxn][Maxn];

il void add(int x1, int y1, int x2, int y2, int val) {
	a[x1][y1] += val; a[x1][y2 + 1] -= val; a[x2 + 1][y1] -= val; a[x2 + 1][y2 + 1] += val;
}

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(q);
	while(q--) {
		int r, c, l, s;
		read(r), read(c), read(l), read(s);
		add(r, c, min(r + l - 1, n), c, s);
		if(r + l <= n && c + 1 <= n) add(r + l, c + 1, r + l, min(c + l, n), -s);
	}
	for(int i = 1; i <= n; i++) {
		for(int j = 1; j <= n; j++) {
			a[i][j] += a[i][j - 1] + a[i - 1][j] - a[i - 1][j - 1];
		}
	}
	for(int i = 1; i <= n; i++) {
		for(int j = 1; j <= n; j++) {
			a[i][j] += a[i - 1][j - 1];
		}
	}
	int ans = 0;
	for(int i = 1; i <= n; i++) {
		for(int j = 1; j <= n; j++) {
			ans ^= a[i][j];
		}
	}
	write(ans);
    Usd();
	return 0;
}

T2 游戏

手玩几组数据后会发现一件事:在某些条件下,双方都有能力使得答案为空集,此时答案必定为 \(0\)。我们来探究一下这个条件。

发现每一次操作会分出两个集合,如果我们每一次都选取较小的那个集合,在 \(\log n\) 次后我们就可以得到空集。所以当 \(m>2\log n\) 的时候,即 \(m>28\) 的时候双方都有能力使得答案为 \(0\)。此时我们只需要考虑 \(m\le 28\) 的情况即可。

这个情况就非常简单了,随意写一个爆搜做到 \(O(nm)\) 的复杂度即可。

#include <bits/stdc++.h>
#define il inline
#define int long long

using namespace std;

const int Maxn = 2e5 + 5;
const int Inf = 1e17;
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("in.txt", "r", stdin); freopen("out.txt", "w", stdout);}
bool Beg;

int n, m, a[Maxn], b[Maxn];
int t[Maxn];

il int dfs(int x, int l, int r) {
	if(l > r) return 0;
	if(x == m + 1) {
		int sm = 0;
		for(int i = l; i <= r; i++) sm += a[i];
		return sm;
	}
	for(int i = l; i <= r; i++) t[i] = a[i];
	int cnt = l, mid = 0;
	for(int i = l; i <= r; i++) if(t[i] % b[x] == 0) a[cnt++] = t[i];
	mid = cnt - 1;
	for(int i = l; i <= r; i++) if(t[i] % b[x] != 0) a[cnt++] = t[i];
	int res1 = dfs(x + 1, l, mid);
	int res2 = dfs(x + 1, mid + 1, r);
	if(x & 1) return min(res1, res2);
	else return max(res1, res2);
}	

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);
	if(m > 28) {write(0); return 0;}
	for(int i = 1; i <= n; i++) read(a[i]);
	for(int i = 1; i <= m; i++) read(b[i]);
	write(dfs(1, 1, n));
	Usd();
	return 0;
}

T3 连通块

先考虑暴力,我们 \(O(n^2)\) 连边,然后单次 \(O(n)\) 判断删掉一个点后最大连通块大小。总复杂度是 \(O(n^2)\) 的。

考虑分别优化这两个部分。先看连边部分,发现两个点的 \(\gcd\) 为合数说明这两个数是某两个质数乘积的倍数。我们枚举这两个质数,找出所有符合条件的数字,则这些数字应该连成一个完全图。利用经典套路,建立一个虚点向这些数字连边即可。

然后我们来优化后面的部分。我们要删去一个点然后求出剩余连通块大小最大值,容易发现我们删去的一定是当前最大连通块上的一个点,并且有割点要尽可能删去割点。考虑建立圆方树,利用子树信息求出答案即可。

#include <bits/stdc++.h>
#define il inline
#define int long long

using namespace std;

const int Maxn = 3e6 + 5, Maxm = 1e7 + 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("in.txt", "r", stdin); freopen("out.txt", "w", stdout);}
bool Beg;

int T;
int n, m, a[Maxn];

int fa[Maxn], sz[Maxn];
int prim[1000005], cnt, vis[Maxm], mnp[Maxm], id[Maxm];
il void init(int n) {
	for(int i = 2; i <= n; i++) {
		if(!vis[i]) prim[++cnt] = i, mnp[i] = i;
		for(int j = 1, x; (x = i * prim[j]) <= n; j++) {
			vis[x] = 1;
			mnp[x] = prim[j];
			if(i % prim[j] == 0) break;
		}
	}
	for(int i = 2; i <= n; i++) if(vis[i] && !vis[i / mnp[i]]) id[i] = ++m;
}

il int find(int x) {return fa[x] == x ? x : fa[x] = find(fa[x]);}
il void merge(int x, int y) {
	x = find(x), y = find(y);
	if(x == y) return ;
	if(sz[x] > sz[y]) swap(x, y);
	sz[y] += sz[x], fa[x] = y;
}

int head[Maxn], edgenum;
struct node {
	int nxt, to;
}edge[Maxn];

il void add(int u, int v) {
//	cout << u << " " << v << '\n';
	edge[++edgenum] = {head[u], v}; head[u] = edgenum;
	edge[++edgenum] = {head[v], u}; head[v] = edgenum;
}

int fac[Maxn], num[Maxn];
il void Div(int x, int t) {
	int tot = 0;
	while(x > 1) {
		int w = mnp[x];
		fac[++tot] = w; num[tot] = 0;
		while(x % w == 0) {
			num[tot]++;
			x /= w; 
		}
	} 
	for(int i = 1; i <= tot; i++) {
		for(int j = i + (num[i] <= 1); j <= tot; j++) {
			if(fac[i] * fac[j] < Maxm) {
				int p = id[fac[i] * fac[j]] + n;
				add(p, t), merge(t, p);
			}
		}
	}
}

int sum, ans;
int dfn[Maxn], low[Maxn], idx, stk[Maxn], top, siz[Maxn];
il void tarjan(int x, int rt) {
	dfn[x] = low[x] = ++idx;
	stk[++top] = x;
	int cld = 0, flg = 0, ret = 0;
	siz[x] = (x <= n);
//	cerr << x << " " << rt << '\n';
	for(int i = head[x]; i; i = edge[i].nxt) {
		int to = edge[i].to;
		if(!dfn[to]) {
			cld++;
			tarjan(to, rt);
			low[x] = min(low[x], low[to]);
			if(low[to] >= dfn[x]) {
				if(x != rt || cld > 1) flg = 1;
				int s = 0;
				while(1) {
					int v = stk[top--];
					siz[x] += siz[v]; s += siz[v];
					if(v == to) break;
				}
				chkmax(ret, s);
			}
		}
		else low[x] = min(low[x], dfn[to]);
	}
	if(x <= n) {
		if(flg) chkmin(ans, max(ret, sum - siz[x]));
		else chkmin(ans, sum - 1);
	}
}

il void init() {
	edgenum = idx = top = 0;
	for(int i = 1; i <= n + m; i++) head[i] = dfn[i] = low[i] = 0;
}

il void solve() {
	init();
	read(n);
	for(int i = 1; i <= n + m; i++) fa[i] = i, sz[i] = (i <= n);
	for(int i = 1; i <= n; i++) read(a[i]);
	for(int i = 1; i <= n; i++) Div(a[i], i);
	int mx = 0, lmx = 0, pos = 0;
	for(int i = 1; i <= n + m; i++) {
		if(fa[i] == i) {
			if(sz[i] >= mx) lmx = mx, mx = sz[i], pos = i;
			else if(sz[i] > lmx) lmx = sz[i];
		}
	}
	ans = sum = mx; 
//	cout << mx << " " << lmx << " " << pos << '\n';
	tarjan(pos, pos);
	write(max(lmx, ans));
}

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(T); init(1e7);
	while(T--) solve();
    Usd();
	return 0;
}

T4 公交路线

考虑对每个点计算有一条路径端点为该点的方案数,总方案数就是总和除以 \(4\),单次询问直接减去对应贡献即可。

考虑一条路径 \((u,v)\)\(u\) 的贡献,我们需要计算另一条路径不与该路径相交的路径数量。直接算并不好算,考虑正难则反。设 \(f(u)\) 表示 \(u\) 子树内经过 \(u\) 的路径总数,\(g(u)\) 表示经过 \(u\) 的所有路径数量。总方案数为 \(\sum f(i)\),而与这条路径相交的路径条数为 \(\sum f(i)+g(\text{LCA}(u,v))\),两者相减即可。

求出 \(f(u)\)\(g(u)\) 是比较容易的,对每种颜色建虚树,转化为树上单点加、链加,离线下来树上差分即可求出。

然后继续考虑计算答案,令 \(F(i)\) 表示 \(i\) 根链上 \(f(i)\) 之和,则答案进一步转化为 \(\sum f(i)-F(u)-F(v)+2F(\text{LCA}(u,v))-g(\text{LCA}(u,v))\)\(\sum f(i)-F(u)-F(v)\) 是容易求出来的,然后对于后面的部分,再次对每种颜色建虚树,枚举 \(\text{LCA}\),将贡献转化为子树加,再做一次树上差分即可。

如此复杂度即为 \(O(n\log n)\),精细实现可以做到 \(O(n)\)

#include <bits/stdc++.h>
#define il inline
#define int long long

using namespace std;

const int Maxn = 2e5 + 5;
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("in.txt", "r", stdin); freopen("out.txt", "w", stdout);}
bool Beg;

int n, m, k;
int c[Maxn];
int head[Maxn], edgenum;
struct node {
	int nxt, to;
}edge[Maxn];

il void add(int u, int v) {
	edge[++edgenum] = {head[u], v}; head[u] = edgenum;
	edge[++edgenum] = {head[v], u}; head[v] = edgenum;
}

int dfn[Maxn], fa[Maxn], mn[18][Maxn], idx;
namespace T {
	int get(int x, int y) {return dfn[x] < dfn[y] ? x : y;}
	il void dfs(int x, int fth) {
		mn[0][dfn[x] = ++idx] = fa[x] = fth;
		for(int i = head[x]; i; i = edge[i].nxt) {
			int to = edge[i].to;
			if(to == fth) continue;
			dfs(to, x);
		}
	}
	il void init() {
		for(int i = 1; i <= 17; i++) {
			for(int j = 1; j + (1 << i) - 1 <= n; j++) {
				mn[i][j] = get(mn[i - 1][j], mn[i - 1][j + (1 << (i - 1))]);
			}
		}
	}
	il int lca(int u, int v) {
		if(u == v) return u;
		if((u = dfn[u]) > (v = dfn[v])) swap(u, v);
		int k = __lg(v - u); u++;
		return get(mn[k][u], mn[k][v - (1 << k) + 1]);
	}
} 

int c1[Maxn], c2[Maxn];
int f[Maxn], F[Maxn], g[Maxn];
int sm = 0;
il void dfs(int x) {
	f[x] += c1[x]; sm += f[x]; 
	F[x] = F[fa[x]] + f[x];
	for(int i = head[x]; i; i = edge[i].nxt) {
		int to = edge[i].to;
		if(to == fa[x]) continue;
		dfs(to); c2[x] += c2[to];
	}
	g[x] = f[x] + c2[x];
}

int ans[Maxn];

namespace VT {
	int head[Maxn], edgenum;
	struct node {
		int nxt, to;
	}edge[Maxn];
	il void add(int u, int v) {
		edge[++edgenum] = {head[u], v};
		head[u] = edgenum;
	}
	int s[Maxn], top = 0;
	il void build(vector <int> &v) {
		edgenum = top = 0;
		s[++top] = v[0]; head[v[0]] = 0;
		for(int i = 1; i < v.size(); i++) {
			int x = v[i], t = s[top], l = T::lca(x, t);
			if(l == t) {
				head[x] = 0; s[++top] = x;
				continue;
			}
			while(dfn[s[top - 1]] > dfn[l]) add(s[top - 1], s[top]), top--;
			if(s[top - 1] != l) {
				head[l] = 0; add(l, s[top]); s[top] = l;
			}
			else add(l, s[top--]);
			head[x] = 0; s[++top] = x;
		}
		for(int i = 1; i < top; i++) add(s[i], s[i + 1]); 
	}
	int sum, col, siz[Maxn], f1[Maxn], g1[Maxn];
	il void dfs1(int x, int fth) {
		siz[x] = (c[x] == col);
		f1[x] = g1[x] = 0;
		for(int i = head[x]; i; i = edge[i].nxt) {
			int to = edge[i].to;
			dfs1(to, x);
			f1[x] += siz[x] * siz[to];
			siz[x] += siz[to];
		}
		g1[x] = siz[x] * (sum - siz[x]);
		c1[x] += f1[x], c2[fth] -= g1[x], c2[x] += g1[x];
	}
	il void solve1(int cc, vector <int> &v) {
		col = cc;
		sum = v.size();
		build(v); int rt = s[1];
		dfs1(rt, 0);
	}
	int ct[Maxn];
	il void dfs2(int x) {
		siz[x] = (c[x] == col);
		for(int i = head[x]; i; i = edge[i].nxt) {
			int to = edge[i].to;
			dfs2(to);
			siz[x] += siz[to];
		}
		if(c[x] == col) ans[x] += (2 * F[x] - g[x]) * (siz[x] - 1);
		for(int i = head[x]; i; i = edge[i].nxt) {
			int to = edge[i].to;
			ct[to] += (2 * F[x] - g[x]) * (siz[x] - siz[to]);
		}
	}
	il void dfs3(int x, int w) {
		w += ct[x];
		if(c[x] == col)	ans[x] += w;
		ct[x] = 0;
		for(int i = head[x]; i; i = edge[i].nxt) {
			int to = edge[i].to;
			dfs3(to, w);
		}
	}
	il void solve2(int cc, vector <int> &v) {
		col = cc;
		sum = v.size();
		int ret = 0;
		for(auto p : v) ret += F[p];
		for(auto p : v) ans[p] += (sm - F[p]) * (sum - 1) - (ret - F[p]);
		build(v); int rt = s[1];
		dfs2(rt); dfs3(rt, 0);
	}
}

vector <int> nod[Maxn];

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), read(k);
	for(int i = 1; i <= n; i++) read(c[i]), nod[c[i]].push_back(i);
	for(int i = 1, u, v; i < n; i++) {
		read(u), read(v);
		add(u, v);
	}
	T::dfs(1, 0), T::init();
	for(int i = 1; i <= k; i++) {
		if(nod[i].empty()) continue;
		sort(nod[i].begin(), nod[i].end(), [](int x, int y){return dfn[x] < dfn[y];});
		VT::solve1(i, nod[i]);
	}
	dfs(1);
	for(int i = 1; i <= k; i++) {
		if(nod[i].empty()) continue;
		VT::solve2(i, nod[i]);
	}
	int res = 0;
	for(int i = 1; i <= n; i++) res += ans[i];
	res >>= 2;
	write(res);
	while(m--) {
		int x; read(x);
		write(res - ans[x]);
	}
    Usd();
	return 0;
}
posted @ 2025-07-07 16:19  UKE_Automation  阅读(57)  评论(0)    收藏  举报