26.5~26.6 新生活前奏

26.5.17 2026 ICPC-Wuhan inv solved 4 Penalty 972 rank 86 Cu

26.5.20 vp 2026 SNCPC solved 7 Penalty 583

26.5.22 vp 2025 CCPC-Nanchang inv solved 7 Penalty 726

26.5.24 2026 HBCPC solved 7 Penalty 648 rank 47 Ag

26.5.29 vp 2025 CCPC-Zhengzhou Regional Solved 3 Penalty 662

半步之遥了,综上所述感觉是吃罚时吃的,遗憾凭什么只有我承担。欠的有点多,也不知道学什么新东西,先补两天题。此外强烈建议以后应该把 vp 调到睡醒午觉。

2026 HBCPC M.拯救猫猫

遗忘的技能点。答案只注意可达性,考虑可撤销并查集。枚举 ban 掉的狗,每次操作相当于使该狗视线两侧的若干连通块联通,检查起点和终点是否在一个连通块里,然后再撤销合并操作即可。显然每个点至多被遍历一次,复杂度 \(\mathcal O(nm)\),我的写法令我写起来有点恶心。

点击查看代码
#include<bits/stdc++.h>
#define mp make_pair
#define eb emplace_back
#define fi first
#define se second
using namespace std;

static const int MAX = 2010;

int dx[] = {0, 0, 1, -1};
int dy[] = {1, -1, 0, 0};
int top, fa[MAX * MAX], siz[MAX * MAX];
struct dsu
{
	int x, fa, siz;
	dsu(int X = 0, int Fa = 0, int Siz = 0) {x = X, fa = Fa, siz = Siz;}
}stk[MAX * MAX << 2];
inline int find(int x) {while(x != fa[x]) x = fa[x]; return x;}
inline void merge(int x, int y)
{
	x = find(x), y = find(y);
	if(x == y) return; 
	if(siz[x] < siz[y]) swap(x, y);
	stk[++top] = dsu(x, fa[x], siz[x]), stk[++top] = dsu(y, fa[y], siz[y]);
	siz[x] += siz[y], fa[y] = x; 
}
inline void back(int lst) {while(top > lst) fa[stk[top].x] = stk[top].fa, siz[stk[top].x] = stk[top].siz, --top;}
inline bool check(int x, int y) {return find(x) == find(y);}

int n, m, Top, v[MAX][MAX], s[MAX][MAX];
int sx, sy, ex, ey;
char c[MAX][MAX];
queue<pair<int, int> > q;
inline int id(int x, int y) {return (x - 1) * m + y;}

inline void solve()
{
	cin >> n >> m, top = 0;
	vector<pair<int, int> > dog;
	for(int i = 1; i <= n; ++i) for(int j = 1; j <= m; ++j) cin >> c[i][j], v[i][j] = s[i][j] = 0;
	for(int i = 1; i <= n; ++i) for(int j = 1; j <= m; ++j)
	{
		fa[id(i, j)] = id(i, j), siz[id(i, j)] = 1;
		if(c[i][j] == 'S') sx = i, sy = j, c[i][j] = '.';
		if(c[i][j] == 'E') ex = i, ey = j, c[i][j] = '.';
		if(c[i][j] == 'U')
		{
			for(int x = i - 1; x >= 1; --x) if(c[x][j] == '.') ++s[x][j]; else break;
			dog.eb(mp(i, j));
		}
		if(c[i][j] == 'D')
		{
			for(int x = i + 1; x <= n; ++x) if(c[x][j] == '.') ++s[x][j]; else break;
			dog.eb(mp(i, j));
		}
		if(c[i][j] == 'L')
		{
			for(int y = j - 1; y >= 1; --y) if(c[i][y] == '.') ++s[i][y]; else break;
			dog.eb(mp(i, j));
		}
		if(c[i][j] == 'R')
		{
			for(int y = j + 1; y <= m; ++y) if(c[i][y] == '.') ++s[i][y]; else break;
			dog.eb(mp(i, j));
		}
	}
	for(int i = 1; i <= n; ++i) for(int j = 1; j <= m; ++j) if(c[i][j] == '.' && !v[i][j] && !s[i][j])
	{
		q.emplace(mp(i, j)), v[i][j] = 1;
		while(!q.empty())
		{
			auto [x, y] = q.front(); q.pop();
			for(int k = 0; k < 4; ++k)
			{
				int tx = x + dx[k], ty = y + dy[k];
				if(tx >= 1 && tx <= n && ty >= 1 && ty <= m && !v[tx][ty] && c[tx][ty] == '.' && !s[tx][ty])
				{
					merge(id(i, j), id(tx, ty));
					v[tx][ty] = 1;
					q.emplace(mp(tx, ty));
				}
			}
		}
	}
	Top = top;
	for(int k = 0; k < (int)dog.size(); ++k)
	{
		int dogx = dog[k].fi, dogy = dog[k].se;
		if(c[dogx][dogy] == 'U')
		{
			for(int i = dogx - 1; i >= 1; --i)
			{
				if(c[i][dogy] == '.')
				{
					if(s[i][dogy] != 1) continue;
					if(i > 1 && c[i - 1][dogy] == '.' && s[i - 1][dogy] <= 1) merge(id(i, dogy), id(i - 1, dogy)); 
					if(dogy > 1 && c[i][dogy - 1] == '.' && s[i][dogy - 1] <= 1) merge(id(i, dogy), id(i, dogy - 1));
					if(dogy < m && c[i][dogy + 1] == '.' && s[i][dogy + 1] <= 1) merge(id(i, dogy), id(i, dogy + 1));
				}
				else break;
			}
			if(check(id(sx, sy), id(ex, ey))) return cout << dogx << " " << dogy << '\n', void();
			back(Top);
		}
		if(c[dogx][dogy] == 'D')
		{
			for(int i = dogx + 1; i <= n; ++i)
			{
				if(c[i][dogy] == '.')
				{
					if(s[i][dogy] != 1) continue;
					if(i < n && c[i + 1][dogy] == '.' && s[i + 1][dogy] <= 1) merge(id(i, dogy), id(i + 1, dogy)); 
					if(dogy > 1 && c[i][dogy - 1] == '.' && s[i][dogy - 1] <= 1) merge(id(i, dogy), id(i, dogy - 1));
					if(dogy < m && c[i][dogy + 1] == '.' && s[i][dogy + 1] <= 1) merge(id(i, dogy), id(i, dogy + 1));
				}
				else break;
			}
			if(check(id(sx, sy), id(ex, ey))) return cout << dogx << " " << dogy << '\n', void();
			back(Top);
		}
		if(c[dogx][dogy] == 'L')
		{
			for(int i = dogy - 1; i >= 1; --i)
			{
				if(c[dogx][i] == '.')
				{
					if(s[dogx][i] != 1) continue;
					if(i > 1 && c[dogx][i - 1] == '.' && s[dogx][i - 1] <= 1) merge(id(dogx, i), id(dogx, i - 1)); 
					if(dogx > 1 && c[dogx - 1][i] == '.' && s[dogx - 1][i] <= 1) merge(id(dogx, i), id(dogx - 1, i));
					if(dogx < n && c[dogx + 1][i] == '.' && s[dogx + 1][i] <= 1) merge(id(dogx, i), id(dogx + 1, i));
				}
				else break;
			}
			if(check(id(sx, sy), id(ex, ey))) return cout << dogx << " " << dogy << '\n', void();
			back(Top);
		}
		if(c[dogx][dogy] == 'R')
		{
			for(int i = dogy + 1; i <= m; ++i)
			{
				if(c[dogx][i] == '.')
				{
					if(s[dogx][i] != 1) continue;
					if(i < m && c[dogx][i + 1] == '.' && s[dogx][i + 1] <= 1) merge(id(dogx, i), id(dogx, i + 1)); 
					if(dogx > 1 && c[dogx - 1][i] == '.' && s[dogx - 1][i] <= 1) merge(id(dogx, i), id(dogx - 1, i));
					if(dogx < n && c[dogx + 1][i] == '.' && s[dogx + 1][i] <= 1) merge(id(dogx, i), id(dogx + 1, i));
				}
				else break;
			}
			if(check(id(sx, sy), id(ex, ey))) return cout << dogx << " " << dogy << '\n', void();
			back(Top);
		}
	}
	cout << "-1 -1" << '\n'; 
}

signed main()
{
	ios::sync_with_stdio(false), cin.tie(nullptr);
	int T = 1; cin >> T;
	while(T--) solve();
	return (0 - 0);
}

2026 HBCPC G.建设高铁

等价于 kruskal 重构树上每个叶子有一个权值,修改操作为改叶子的权值,询问操作为选取前 \(k_i\) 大的叶子求他们的 LCA。首先有结论:若干个叶子的 LCA 等价于其中 dfs 序编号最大的叶子和最小的叶子的 LCA。考虑对权值做值域线段树,节点上存 dfs 序信息,每次查询即在线段树上二分,然后直接倍增求 LCA。比较难搞的是权值有重复,还要考虑相同权值优先小编号的问题,可以重新带编号赋值做二次离散化去重。复杂度 \(\mathcal O(q \log (n + m))\),赛时没考虑好去重的问题导致没出。

点击查看代码
#include<bits/stdc++.h>
#define mp make_pair
#define eb emplace_back
#define fi first
#define se second
using namespace std;

static const int MAX = 500010; 
static const int INF = INT_MAX; 

int n, m, q, d[MAX << 1], cnt;
struct edge{int x, y, z;}t;
vector<edge> E;
vector<int> G[MAX << 2];
struct operation{int op, c, t, k;}a[MAX];

int val[MAX << 1], fa[MAX << 1];
int find(int x) {return x == fa[x] ? x : fa[x] = find(fa[x]);}

int dep[MAX << 2], f[MAX << 2][20], id[MAX << 2], rid[MAX << 2], tot;
void dfs(int now, int father)
{
	dep[now] = dep[father] + 1, f[now][0] = father;
	id[now] = ++tot, rid[tot] = now;
	for(int i = 1; i <= __lg(dep[now]); ++i) f[now][i] = f[f[now][i - 1]][i - 1];
	for(auto to:G[now]) if(to != father) dfs(to, now);
}
inline int LCA(int x, int y)
{
	if(dep[x] < dep[y]) swap(x, y);
	while(dep[x] > dep[y]) x = f[x][__lg(dep[x] - dep[y])];
	if(x == y) return x;
	for(int i = __lg(dep[x]); ~i; --i) if(f[x][i] != f[y][i]) x = f[x][i], y = f[y][i];
	return f[x][0]; 
}

int N, sum[MAX << 2], maxx[MAX << 2], minx[MAX << 2];
inline void pushup(int i)
{
	sum[i] = sum[i << 1] + sum[i << 1 | 1];
	maxx[i] = max(maxx[i << 1], maxx[i << 1 | 1]);
	minx[i] = min(minx[i << 1], minx[i << 1 | 1]);
}
void build(int i, int l, int r)
{
	if(l == r) return minx[i] = INF, maxx[i] = -INF, void();
	int mid = (l + r) >> 1;
	build(i << 1, l, mid), build(i << 1 | 1, mid + 1, r);
	pushup(i);
}
void modify(int i, int l, int r, int x, int k)
{
	if(l == r)
	{
		if(k == 0) sum[i] = 0, minx[i] = INF, maxx[i] = -INF;
		else sum[i] = 1, minx[i] = maxx[i] = k;
		return;
	}
	int mid = (l + r) >> 1;
	if(x <= mid) modify(i << 1, l, mid, x, k); else modify(i << 1 | 1, mid + 1, r, x, k);
	pushup(i);
}
inline void modify(int x, int k) {modify(1, 1, N, x, k);}
pair<int, int> query(int i, int l, int r, int s)
{
	if(s == sum[i]) return mp(minx[i], maxx[i]);
	int mid = (l + r) >> 1;
	if(s <= sum[i << 1 | 1]) return query(i << 1 | 1, mid + 1, r, s);
	else
	{
		pair<int, int> res = query(i << 1, l, mid, s - sum[i << 1 | 1]);
		return mp(min(res.fi, minx[i << 1 | 1]), max(res.se, maxx[i << 1 | 1]));
	}
}
inline pair<int, int> query(int s) {return query(1, 1, N, s);}

inline void solve()
{
	cin >> n >> m >> q;
	for(int i = 1; i <= n; ++i) cin >> val[i], d[++cnt] = val[i];
	for(int i = 1; i <= (n << 1); ++i) fa[i] = i; 
	for(int i = 1; i <= m; ++i) cin >> t.x >> t.y >> t.z, E.eb(t);
	sort(E.begin(), E.end(), [&](edge a, edge b){return a.z < b.z;});
	
	for(int i = 1; i <= q; ++i) 
	{
		cin >> a[i].op;
		if(a[i].op == 1) cin >> a[i].c >> a[i].t, d[++cnt] = a[i].t;
		if(a[i].op == 2) cin >> a[i].k; 
	}
	sort(d + 1, d + cnt + 1, [&](int a, int b){return a < b;});
	cnt = unique(d + 1, d + cnt + 1) - d - 1;
	for(int i = 1; i <= n; ++i) val[i] = lower_bound(d + 1, d + cnt + 1, val[i]) - d;
	for(int i = 1; i <= q; ++i) if(a[i].op == 1) a[i].t = lower_bound(d + 1, d + cnt + 1, a[i].t) - d;
	cnt = 0;
	for(int i = 1; i <= n; ++i) val[i] = val[i] * n - i, d[++cnt] = val[i];
	for(int i = 1; i <= q; ++i) if(a[i].op == 1) a[i].t = a[i].t * n - a[i].c, d[++cnt] = a[i].t;
	sort(d + 1, d + cnt + 1, [&](int a, int b){return a < b;});
	cnt = unique(d + 1, d + cnt + 1) - d - 1;
	for(int i = 1; i <= n; ++i) val[i] = lower_bound(d + 1, d + cnt + 1, val[i]) - d;
	for(int i = 1; i <= q; ++i) if(a[i].op == 1) a[i].t = lower_bound(d + 1, d + cnt + 1, a[i].t) - d;
	N = cnt, cnt = 0; 
	
	for(auto [x, y, z]:E)
	{
		x = find(x), y = find(y);
		if(x == y) continue;
		++cnt, val[cnt + n] = z;
		G[cnt + n].eb(x), fa[x] = cnt + n;
		G[cnt + n].eb(y), fa[y] = cnt + n;
		if(cnt == n - 1) break;
	}
	dfs(cnt + n, 0);
	
	build(1, 1, N);
	for(int i = 1; i <= n; ++i) modify(val[i], id[i]); 
	for(int i = 1; i <= q; ++i)
	{
		pair<int, int> res;
		if(a[i].op == 1) modify(val[a[i].c], 0), val[a[i].c] = a[i].t, modify(val[a[i].c], id[a[i].c]);
		if(a[i].op == 2) {if(a[i].k == 1) cout << 0 << '\n'; else res = query(a[i].k), cout << val[LCA(rid[res.fi], rid[res.se])] << '\n';}
	}
}

signed main()
{
	ios::sync_with_stdio(false), cin.tie(nullptr);
	int T = 1; //cin >> T;
	while(T--) solve();
	return (0 - 0);
}

2025 CCPC-Zhengzhou reg I.Dumb Problem II

\(k\) 个长度为 \(n\) 的随机排列本质不同的前缀 \(\max\) 数字集合个数。什么时候会数数?

记得到的前缀 \(\max\) 集合为 \(S\),考虑从大到小依次插入 \(x = n, n - 1, ..., 1\)。那么只会有两种情况:

  • \(x\)\(S\) 中,那么 \(x\) 必须在比它的所有数的前面,只有一个插入的位置。
  • \(x\) 不在 \(S\) 中,此时前面已经插入了 \(n - x\) 个数,\(x\) 可以在除了首位的任意一个位置,那么 \(x\)\(n - x\) 个位置可以插入。

所以 \(S\) 出现的频率为 \(F_S = \prod\limits_{x \notin S} (n - x)\),显然对于随机生成的一个排列,它导出集合为 \(S\) 的概率为 \(P_S = \dfrac{\prod\limits_{x \notin S} (n - x)}{n!}\)

然后考虑随机选择了 \(k\) 个集合,要求不同集合的期望数。也就是对于所有集合 \(S\),在抽到的 \(k\) 个排列中至少出现一次的概率之和。接下来推柿子:

\[\begin{align*} E &= \sum_{S} (1 - (1 - P_{S})^{k}) \\ &= \sum_{S} (1 - \sum_{i = 0}^{k} \binom{k}{i} (-1)^{i} P_{S}^{i}) \\ &= \sum_{S} \sum_{i = 1}^{k} \binom{k}{i} (-1)^{i + 1} P_{S}^{i} \\ &= \sum_{i = 1}^{k} \binom{k}{i} (-1)^{i + 1} \sum_{S} P_{S}^{i} \\ &= \sum_{i = 1}^{k} \binom{k}{i} (-1)^{i + 1} \sum_{S} (\dfrac{\prod\limits_{x \notin S} (n - x)}{n!})^{i} \\ &= \sum_{i = 1}^{k} \binom{k}{i} (-1)^{i + 1} (n!)^{-i} \sum_{S} \prod\limits_{x \notin S} (n - x) \\ \end{align*} \]

对于后面这个枚举子集我们考虑一个多项式展开的意义:考虑有若干个数 \(A_i\),对于 \((1 + A_1) (1 + A_2) ... (1 + A_n) = \prod\limits_{i = 1}^{n} (1 + A_i)\) 的完全展开,得到的每一项实质上是 \(n\) 个括号中要么选 \(1\) 要么选 \(A_i\) 相乘的结果,选 \(1\) 等价于 \(i \in S\),选 \(A_i\) 等价于 \(i \notin S\),于是展开后的每一项都对应着唯一一个 \(S \subseteq {1, 2, ..., n}\)。综上所述:

\[\begin{align*} \prod\limits_{i = 1}^{n} (1 + A_i) &= \sum_{S \subseteq {1, 2, ..., n}} \prod\limits_{i \notin S} A_i \\ \Rightarrow E &= \sum_{i = 1}^{k} \binom{k}{i} (-1)^{i + 1} (n!)^{-i} \sum_{S} \prod\limits_{x \notin S} (n - x) \\ E &= \sum_{i = 1}^{k} \binom{k}{i} (-1)^{i + 1} (n!)^{-i} \prod\limits_{j = 0}^{n - 1} (1 + j^{i}) \end{align*} \]

预处理组合数和阶乘逆元,至此做到 \(\mathcal O(nk)\)

点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;

static const int MAX = 5010;
static const int mod = 998244353;
int n, k, fac, ans;
int C[MAX][MAX];

inline int qpow(int a, int b)
{
	int res = 1;
	while(b)
	{
		if(b & 1) res = res * a % mod;
		a = a * a % mod, b >>= 1;
	}
	return res;
}

inline void solve()
{
	cin >> n >> k, fac = 1;
	for(int i = 0; i <= k; ++i) C[i][0] = 1;
	for(int i = 1; i <= k; ++i) for(int j = 1; j <= i; ++j) C[i][j] = (C[i - 1][j] + C[i - 1][j - 1]) % mod;
	for(int i = 1; i <= n; ++i) fac = (fac * i) % mod;
	for(int i = 1; i <= k ;++i)
	{
		int res = 1;
		for(int j = 0; j < n; ++j) res = res * (1 + qpow(j, i)) % mod;
		if(i & 1) ans += ((C[k][i] * qpow(qpow(fac, i), mod - 2) % mod) * res) % mod, ans %= mod;
		else ans -= ((C[k][i] * qpow(qpow(fac, i), mod - 2) % mod) * res) % mod, ans = (ans % mod + mod) % mod;
	}
	cout << ans << '\n'; 
}

signed main()
{  
	ios::sync_with_stdio(false), cin.tie(nullptr);
	int T = 1; //cin >> T;
	while(T--) solve();
	return (0 - 0);
}

2025 CCPC-Zhengzhou reg J.Subrectangle Count

\(x = c_{i,j} = a_{i} \oplus b_{j}\),通过条件可以得到一些柿子:

\[\begin{align*} & x \oplus (x + 1) = a_{i} \oplus b_{j} \oplus a_{i} \oplus b_{j + 1} = b_{j} \oplus b_{j + 1} \\ & x \oplus (x + 2) = a_{i} \oplus b_{j} \oplus a_{i + 1} \oplus b_{j} = a_{i} \oplus a_{i + 1} \\ & (x + 1) \oplus (x + 3) = a_{i} \oplus b_{j + 1} \oplus a_{i + 1} \oplus b_{j + 1} = a_{i} \oplus a_{i + 1} \\ & (x + 2) \oplus (x + 3) = a_{i + 1} \oplus b_{j} \oplus a_{i + 1} \oplus b_{j + 1} = b_{j} \oplus b_{j + 1} \\ \end{align*} \]

对于序列 \(b\) 要满足 \(x \oplus (x + 1) = (x + 2) \oplus (x + 3) = b_{j} \oplus b_{j + 1}\),只考虑二进制下后三位:

  • \(x = 000_2, x + 1 = 001_2, x + 2 = 010_2, x + 3 = 011_2\),那么 \(x \oplus (x + 1) = 001_2, (x + 2) \oplus (x + 3) = 001_2\),因此 \(x \oplus (x + 1) = (x + 2) \oplus (x + 3)\),符合。
  • \(x = 001_2, x + 1 = 010_2, x + 2 = 011_2, x + 3 = 100_2\),那么 \(x \oplus (x + 1) = 011_2, (x + 2) \oplus (x + 3) = 111_2\),因此 \(x \oplus (x + 1) \neq (x + 2) \oplus (x + 3)\),不符合。
  • \(x = 010_2, x + 1 = 011_2, x + 2 = 100_2, x + 3 = 101_2\),那么 \(x \oplus (x + 1) = 001_2, (x + 2) \oplus (x + 3) = 001_2\),因此 \(x \oplus (x + 1) = (x + 2) \oplus (x + 3)\),符合。
  • \(x = 011_2, x + 1 = 100_2, x + 2 = 101_2, x + 3 = 110_2\),那么 \(x \oplus (x + 1) = 111_2, (x + 2) \oplus (x + 3) = 011_2\),因此 \(x \oplus (x + 1) \neq (x + 2) \oplus (x + 3)\),不符合。

综上所述,\(b_{j} \oplus b_{j + 1}\) 首先必须是偶数(即 \(x\) 为偶数),且容易得出 \(b_{j} \oplus b_{j + 1} = 1\)。接下来考虑序列 \(a\) 的性质,记 \(D = a \oplus a_{i + 1} = x \oplus (x + 2)\),那么不妨令 \(x = 2k\) ,则 \(D = 2k \oplus (2k + 2) = 2(k \oplus (k + 1))\)。有结论 \(x \oplus (x + 1)\) 必须形如 \(2^p - 1\),所以可得出 \(D \equiv 2^p - 2 (\mod 2^p)\),经过计算可得 \(x \equiv 2^{p - 1} - 2 (\mod 2^p)\)

据此,我们要完成三步:

  • 找出所有 \(b_j \oplus b_{j + 1} = 1\)\(j\)
  • 找出所有 \(a_i \oplus a_{i + 1} = 2^p - 2\)\(i\)
  • 找出 \(b_j \mod 2^p = (a_i \mod 2^p) \oplus (2^{p - 1} - 2)\)

上述过程可以用 trie 实现,复杂度 \(\mathcal O((n + m) \log V)\)

点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;

static const int MAX = 200010;
static const int MAXH = 30; 
int n, m, ans, a[MAX], b[MAX];

struct Trie
{
	int cnt, ch[MAX << 3][2], p[MAX << 3];
	inline void insert(int x)
	{
		int now = 1; 
		for(int i = 0; i < MAXH; ++i)
		{
			if(!ch[now][(x >> i) & 1]) ch[now][(x >> i) & 1] = ++cnt;
			now = ch[now][(x >> i) & 1];
		}
		++p[now]; 
	}
	inline int query(int x, int t)
	{
		int now = 1;
		for(int i = 0; i < t; ++i) 
		{
			if(!ch[now][(x >> i) & 1]) return 0;
			now = ch[now][(x >> i) & 1];
		}
		return p[now];
	}
	void dfs(int now)
	{
		if(ch[now][0]) dfs(ch[now][0]), p[now] += p[ch[now][0]];
		if(ch[now][1]) dfs(ch[now][1]), p[now] += p[ch[now][1]];
	}
}t[2]; 

inline void solve()
{
	cin >> n >> m, ans = 0;
	for(int i = 1; i <= t[0].cnt; ++i) t[0].ch[i][0] = t[0].ch[i][1] = t[0].p[i] = 0;
	for(int i = 1; i <= t[1].cnt; ++i) t[1].ch[i][0] = t[1].ch[i][1] = t[1].p[i] = 0;
	t[0].cnt = t[1].cnt = 1;
	for(int i = 1; i <= n; ++i) cin >> a[i];
	for(int i = 1; i <= m; ++i) cin >> b[i];
	for(int i = 1; i < m; ++i) if((b[i] ^ b[i + 1]) == 1) t[b[i] & 1].insert((b[i]));
	t[0].dfs(1), t[1].dfs(1);
	for(int i = 1; i < n; ++i) if(__builtin_popcountll((a[i] ^ a[i + 1]) + 2) == 1)
	{
		int tmp = __builtin_ctzll((a[i] ^ a[i + 1]) + 2);
		ans += t[a[i]&1].query(((1ll << (tmp - 1)) - 2) ^ a[i], tmp); 
	}
	cout << ans << '\n';
}

signed main()
{
	ios::sync_with_stdio(false), cin.tie(nullptr);
	int T = 1; cin >> T;
	while(T--) solve();
	return (0 - 0);
}

2025 JXCPC D.挑战图同构

定义最短路为经过的最长路径长,给你两张无向图,判断是否两张图中两两点对的最短路都一样长。考虑做一个模拟最小生成树的过程,每次对于一个权值 \(v\),把 \(D_{(x,y)} \leq v\) 的边全部并查集合并,只要两张图的并查集是完全相同的,那么该定义下的最短路就是完全相同的。判断并查集是否相同可以采用随机异或哈希,初始给并查集上每个点赋一个随机值,合并的时候做异或操作;枚举边权的时候统计一下可能发生变化的点判断即可,复杂度 \(\mathcal O((m_1 + m_2) \log (m_1 + m_2))\)

点击查看代码
#include<bits/stdc++.h>
#define int long long
#define ins insert
using namespace std;

static const int MAX = 200010;

mt19937_64 engine(time(NULL)); 
int n, m0, m1, flag;
int p0, p1, cnt, d[MAX << 1];
struct edge{int x, y, z;}t;
vector<edge> E[2];

struct DSU
{
	int fa[MAX], val[MAX];
	int find(int x) {return x == fa[x] ? x: fa[x] = find(fa[x]);}
	inline bool merge(int x, int y)
	{
		x = find(x), y = find(y);
		return x == y ? 0 : (fa[y] = x, val[x] ^= val[y], 1);
	}
}F[2];

inline void solve()
{
	cin >> n >> m0 >> m1, cnt = flag = 0;
	E[0].clear(), E[1].clear(), p0 = 0, p1 = 0;
	for(int i = 1; i <= n; ++i) F[0].fa[i] = F[1].fa[i] = i, F[0].val[i] = F[1].val[i] = engine();
	for(int i = 1; i <= m0; ++i) cin >> t.x >> t.y >> t.z, E[0].emplace_back(t), d[++cnt] = t.z;
	for(int i = 1; i <= m1; ++i) cin >> t.x >> t.y >> t.z, E[1].emplace_back(t), d[++cnt] = t.z; 
	sort(E[0].begin(), E[0].end(), [&](edge x, edge y){return x.z < y.z;});
	sort(E[1].begin(), E[1].end(), [&](edge x, edge y){return x.z < y.z;});
	sort(d + 1, d + cnt + 1, [&](int x, int y){return x < y;});
	cnt = unique(d + 1, d + cnt + 1) - d - 1;
	for(int i = 1; i <= cnt; ++i)
	{
		set<int> s; 
		while(p0 < (int)E[0].size() && E[0][p0].z <= d[i])
		{
			auto [x, y, z] = E[0][p0];
			++p0;
			if(F[0].merge(x, y)) s.ins(x), s.ins(y);
		}
		while(p1 < (int)E[1].size() && E[1][p1].z <= d[i])
		{
			auto [x, y, z] = E[1][p1];
			++p1;
			if(F[1].merge(x, y)) s.ins(x), s.ins(y);
		}
		for(auto x:s) flag |= F[0].val[F[0].find(x)] != F[1].val[F[1].find(x)];
	}
	puts(flag ? "NO" : "YES");
}

signed main()
{
	ios::sync_with_stdio(false), cin.tie(nullptr);
	int T = 1; cin >> T;
	while(T--) solve();
	return (0 - 0);
}

2026 ICPC-Shenzhen inv L.Telepathy

显示出思维差异。把 \(S\) 中的数视为左括号,非 \(S\) 中的数视为右括号。对序列做括号匹配,因为 \(|S| < \dfrac{k}{2}\),所以必定存在未匹配的右括号,选择最后一个未匹配的右括号加入 \(S\)。因为最后一个右括号变成了左括号,它的后面必然不可能有右括号了,所以如果对新的序列再做括号匹配,这个新的左括号一定不会被匹配。因此再模拟一遍括号匹配,输出栈底残留的元素即为答案。

点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;

static const int MAX = 10000010;

string S;
int n, m, x, ans, a[MAX];

inline void solve()
{
	cin >> n >> m;
	stack<int> stk;
	for(int i = 1; i <= n; ++i) a[i] = 0;
	for(int i = 1; i <= m + (S == "Bob"); ++i) cin >> x, a[x] = 1;
	for(int i = 1; i <= n; ++i) if(a[i]) stk.push(i); else {if(!stk.empty()) stk.pop(); else ans = i;} 
	if(S == "Alice") {
		for(int i = 1; i <= n; ++i) if(a[i]) cout << i << " ";
		cout << ans << '\n';
	} else {
		while(!stk.empty()) ans = stk.top(), stk.pop();
		cout << ans << '\n';
	}
}

signed main()
{
	ios::sync_with_stdio(false), cin.tie(nullptr);
	cin >> S; 
	int T = 1; cin >> T;
	while(T--) solve();
	return (0 - 0);
}

2026 ICPC-Shenzhen inv F.Astra

考虑定根怎么做。一个点的 sg 值等于先从这个点出发选取一条 \(\leq k\) 的路径,去掉路径上的点后得到若干棵分离的互不影响的子树,把这些子树的 sg 值异或起来;然后对于所有选取路径的情况取 mex。这里可以 dfs(外层做 dp) 套 dfs(内层暴力枚举选取路径)做到 \(\mathcal O(n^2)\)

考虑对所有点换根怎么做。原来方向的 sg 值是不会改变的,只需要考虑选取从当前点到原定根的路径即可,这部分的影响再做内层 dfs 后异或上就好了。综上总复杂度在 \(\mathcal O(n^2)\),不是很好写。

点击查看代码
#include<bits/stdc++.h>
#define int long long
#define eb emplace_back
using namespace std;

static const int MAX = 2010;

int n, k, x, y, f[MAX], vis[MAX], sg[MAX];
vector<int> G[MAX];
char ans[MAX];

void get(int now, int father, int lenth, int res)
{
	res ^= f[now];
	if(res <= n) vis[res] = 1;
	if(!lenth) return;
	for(auto to:G[now]) if(to != father) get(to, now, lenth - 1, res ^ sg[to]);
}
int calc(int now, int father)
{
	for(int i = 0; i <= n; ++i) vis[i] = 0;
	get(now, father, k - 1, 0);
	for(int i = 0; i <= n; ++i) if(!vis[i]) return i;
	return n;
}
void dfs1(int now, int father)
{
	f[now] = 0;
	for(auto to:G[now]) if(to != father) dfs1(to, now), f[now] ^= sg[to];
	sg[now] = calc(now, father);
}
void dfs2(int now, int father, int res)
{
	ans[now - 1] = '1' - !(res ^ f[now]);
	int tmp = sg[now];
	for(auto to:G[now]) if(to != father) f[now] ^= sg[to] ^ res, sg[now] = calc(now, to), dfs2(to, now, sg[now]), f[now] ^= sg[to] ^ res;
	sg[now] = tmp;
}

inline void solve()
{
	cin >> n >> k, ans[n] = '\0';
	for(int i = 1; i < n; ++i) cin >> x >> y, G[x].eb(y), G[y].eb(x);
	dfs1(1, 0), dfs2(1, 1, 0), cout << ans << '\n';
	for(int i = 1; i <= n; ++i) G[i].clear();
}

signed main()
{
	ios::sync_with_stdio(false), cin.tie(nullptr);
	int T = 1; cin >> T;
	while(T--) solve();
	return (0 - 0);
}

对于简单的事情总会有莫名其妙的过度自信,如果还不能认识到自己的平庸,将对伟大的渴望收敛于内而不外显,可能这辈子都无法成功。题感觉还是很能练的,最终还是输在不会博弈和数数上。

2026 蓝桥杯国A 魔法前缀

多年前的回旋镖。。。删掉一个前缀等价于在 trie 树上断开一条边。转化题意后,等价于在 trie 树上做 AGC017D。得到 \(sg_u = \oplus_{(u, v) = 1} sg_v + 1\),最后看 \(sg_0\) 是不是 \(0\) 即可。复杂度 \(\mathcal O(\Sigma |S|)\)

点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;

static const int MAX = 1000010;

int n, cnt, ch[MAX][26], sg[MAX];
string s;

inline void solve()
{
	for(int i = 0; i <= cnt; ++i) memset(ch[i], 0, sizeof ch[i]), sg[i] = 0;
	cin >> n, cnt = 0;
	for(int i = 1; i <= n; ++i)
	{
		cin >> s;
		int now = 0; 
		for(int j = 0; j < (int)s.size(); ++j)
		{
			int to = s[j] - 'a'; 
			if(!ch[now][to]) ch[now][to] = ++cnt;
			now = ch[now][to];
		}
	}
	function<void(int)> dfs = [&dfs](int now) {for(int i = 0; i < 26; ++i) if(ch[now][i]) dfs(ch[now][i]), sg[now] ^= sg[ch[now][i]] + 1;}; 
	dfs(0);
	cout << (sg[0] ? "XiaoLan" : "XiaoQiao") << '\n';
}

signed main()
{
	ios::sync_with_stdio(false), cin.tie(nullptr);
	int T = 1; cin >> T;
	while(T--) solve();
	return (0 - 0);
}

2026 蓝桥杯国A 神秘排列

简单数数不会做系列。假设存在一个平衡位置 \(i\),它的数值是 \(x\),左边有 \(k\) 个数比它小,那么左边就会有 \(i - 1 - k\) 个数比它大,因为是排列,所以右侧就会有 \(n - x - (i - 1 - k)\) 个数比它大。我们需要使 \(k = n - x - (i - 1 - k)\),解得 \(i = n - x + 1\)。这意味着一个位置若是平衡位置,其数值必须对应降序排列。只需要枚举平衡位置的数量,钦定一些平衡位置,剩下的位置全部错排就不会重复计算,答案即为 \(\sum_{i = \lceil \frac{n}{2} \rceil}^{n} \binom{n}{i} D_{n - i}\)。预处理错排列数和阶乘,复杂度 \(\mathcal O(n)\)

点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;

static const int MAX = 1e7 + 10;
static const int mod = 1e9 + 7; 

int n, ans, fac[MAX], inv[MAX], D[MAX];

inline int qpow(int a, int b) {int res = 1; while(b) {if(b & 1) (res *= a) %= mod; (a *= a) %= mod, b >>= 1;} return res;}
inline int C(int N, int M) {return N >= M ? ((inv[M] * inv[N - M] % mod) * fac[N] % mod) : 0;}

inline void solve()
{
	cin >> n;
	fac[0] = 1; 
	for(int i = 1; i <= n; ++i) fac[i] = fac[i - 1] * i % mod;
	D[0] = 1, D[1] = 0, D[2] = 1;
	for(int i = 3; i <= n; ++i) D[i] = (i - 1) * ((D[i - 1] + D[i - 2]) % mod) % mod;
	inv[n] = qpow(fac[n], mod - 2);
	for(int i = n - 1; ~i; --i) inv[i] = inv[i + 1] * (i + 1) % mod;
	for(int i = (n - 1) / 2 + 1; i <= n; ++i) (ans += C(n, i) * D[n - i] % mod) %= mod;
	cout << ans << '\n';
}

signed main()
{
	ios::sync_with_stdio(false), cin.tie(nullptr);
	int T = 1; //cin >> T;
	while(T--) solve();
	return (0 - 0);
}
posted @ 2026-05-26 10:42  LgxTpre  阅读(55)  评论(0)    收藏  举报