2026寒假做题记录

不断更新中...

Codeforces Round 1069 (Div. 2)

A. Little Fairy's Painting

开桶,模拟过程即可,发现 \(c_i\) 不变便跳出模拟,一定不会超时。

B. XOR Array

利用异或前缀和,设 \(b_i=a_1 \xor a_2\^{}...\^{}a_i\),则 \(b_{l-1}=b_r\),其他的 \(b_i\) 互不相等。答案\(a_i=b_i\^{}b_{i-1}\)

C. Needle in a Haystack

字符串处理。

D. Wishing Cards

有意思的一个DP。

一眼就是 DP 题, 设 \(d_{i,j,k}\) 为前 i 个点,总礼物价值 j,最大礼物为 k 时对答案的贡献。容易写出方程:
\(d_{i,j,k}=MAX\{d_{i-1,j,k}, MAX\{d_{i-1,j-k,y}+(k-y)\times(n-i+1)\}\}\)

拆开得:
\(d_{i,j,k}=MAX\{d_{i-1,j,k}, MAX_{y=0}^{k-1}\{d_{i-1,j-k,y}-y\times(n-i+1)\}+k(n-i+1)\}\)

这样写时间复杂度是 \(O(n\times k^3)\) 的。容易想到每次预处理 \(MAX_{y=0}^{k-1}\{d_{i-1,j-k,y}-y\times(n-i+1)\}\) 使时间复杂度降为 \(O(n\times k^2)\),但这依旧会超时。

注意到对于最佳答案的情况,对于所有选择放入礼物的点,他们的 \(a_i\) 一定是单调上升的,否则答案会更劣。这也就意味着我们只需要考虑单调上升的那些点,也就将 n 个点变为了 k 个点,时间复杂度 \(O(k^3)\)

#include <stdio.h>
#include <algorithm>

int n, m;
int a[100003];
int d[363][363], f[363][363];

inline void kagari () {
	scanf("%d %d", &n, &m); 
	int bnt = 0, mxb = 0;
	for (int i = 1; i <= n; ++i) scanf("%d", &a[i]);
	for (int i = 1; i <= n; ++i) 
		if (a[i] > mxb) mxb = a[i];
		else a[i] = 0;
	for (int i = 1; i <= m; ++i) for (int j = 1; j <= m; ++j) d[i][j] = f[i][j] = -n * m;
	f[0][0] = 0;
	for (int i = 1; i <= n; ++i) if (a[i]) {
		for (int y = 1; y <= m; ++y) 
			for (int x = 1; x <= m; ++x)
				f[x][y] = std:: max(d[x][y] - y * (n - i + 1), f[x][y - 1]);
		for (int k = 1; k <= a[i]; ++k) 
			for (int j = k; j <= m; ++j) 
				d[j][k] = std:: max(d[j][k], k * (n - i + 1) + f[j - k][k]);
	}
	int ans = 0;
	for (int j = 1; j <= m; ++j) 
		for (int k = 1; k <= m; ++k) 
			ans = std:: max(d[j][k], ans);
	printf("%d\n", ans);
	return;
}

int main () {
	int t; scanf("%d", &t);
	while (t--) kagari();
	return 0;
}

CF2150C Limited Edition Shop

Jan 30th 2026

DP + 线段树

\(b_{pos_i}=a_i\)
再设 \(d_{i \ j}\) 表示 A 中前 i 个数和 B 中前 j 个数均被使用,且 \(b_j\) 属于 B 的最大价值。

当所有 \(d_{i-1 \ j}\) 均被处理完后,考虑所有 \(j∈[0, pos_{a_i}]\)\(j>pos_{a_i}\) 的情况与现在无关),计算 \(d_{i \ j}\)

分类讨论 \(a_i\) 的处理方式:

  1. 放入 A 中,则 \(b_{pos_i}\) 前的所有数都可以 +=v[a[i]],即:
    \(d_{i \ j} = d_{i-1 \ j} + v_{a_i}, j∈[0, pos_{a_i})\)
    更新 \(d_{i \ 0 },\ d_{i \ 1 },\ ...,\ d_{i \ pos_i-1 }\) 即可;

  2. 放入 B 中,更新 \(d_{i \ pos_i }\) 的值:
    \(d_{i \ pos_i } = max\{d_{i-1 \ j}\}, j∈[0, pos_{a_i})\)
    也就是:
    \(d_{i \ pos_i } = max\{d_{i-1 \ 0}, d_{i-1 \ 1}, ..., d_{i-1 \ pos_{a_i}-1}\}\)

发现 i 维度是可以不要的,转化成:

\(f_0, f_1, ..., f_{pos[i]-1} += v_{a_i}\)
\(f_{pos_i} = max\{f_j\}, 0≤j<pos_i\)

这是区间加、单点加与查询区间最大值的操作,可以用线段树来解决。

本质是将 i->i+2,i+3,... 限制为 i->i+1 的转移,由此删除 i 维度的转移,并发现 j 维度的转移复杂度可控制为 log 级别,用 nlogn 的时间复杂度解决此题。

#include <stdio.h>
#include <algorithm>
#define ll long long 
#define MODN 1000000007ll

int n, m, a[200003], b[200003], c[200003], pos[200003];
ll v[200003];

struct NODE {
	int l, r;
	ll x, lz;
}t [1600003];
inline void update (int i) {
	t[i].x = std:: max(t[i<<1].x, t[i<<1|1].x);
}
inline void download (int i) {
	if (!t[i].lz) return;
	t[i<<1].lz += t[i].lz, t[i<<1|1].lz += t[i].lz;
	t[i<<1].x += t[i].lz, t[i<<1|1].x += t[i].lz;
	t[i].lz = 0;
	update(i);
}
void build (int i, int l, int r) {
	t[i].l = l, t[i].r = r, t[i].x = t[i].lz = t[i<<1].x = t[i<<1].lz = t[i<<1|1].x = t[i<<1|1].lz = 0;
	if (l == r) return;
	build(i << 1, l, l + r >> 1);
	build(i<<1|1, (l+r>>1) + 1, r);
}
ll query (int i, int l, int r) {
	if (l <= t[i].l && t[i].r <= r) 
		return t[i].x;
	download(i);
	ll res = -200000000000003ll;
	if (t[i<<1].r >= l) res = std:: max(res, query(i << 1, l, r));
	if (t[i<<1|1].l <= r) res = std:: max(res, query(i<<1|1, l, r));
	update(i);
	return res;
}
void change (int i, int l, int r, ll x) {
	if (l <= t[i].l && t[i].r <= r) {
		t[i].x += x, t[i].lz += x;
		return;
	}
	download(i);
	if (t[i<<1].r >= l) change(i << 1, l, r, x);
	if (t[i<<1|1].l <= r) change(i<<1|1, l, r, x);
	update(i);
}

inline void kagari () {
	scanf("%d", &n);
	for (int i = 1; i <= n; ++i) scanf("%lld", &v[i]);
	for (int i = 1; i <= n; ++i) scanf("%d", &a[i]);
	for (int i = 1; i <= n; ++i) scanf("%d", &b[i]), c[b[i]] = i;
	for (int i = 1; i <= n; ++i) pos[i] = c[a[i]];
	build(1, 0, n);
	for (int i = 1; i <= n; ++i) {
		ll x = query(1, 0, pos[i] - 1);
		ll y = query(1, pos[i], pos[i]);
		if (x > y) change(1, pos[i], pos[i], x - y); // f[pos[i]] = max{f[j]}, 0<=j<pos[i]
		change(1, 0, pos[i] - 1, v[a[i]]);	// f[0], f[1], ..., f[pos[i]-1] += v[a[i]]
	}
	ll ans = query(1, 0, n);
	printf("%lld\n", ans);
	return;
}
int main () {
	int t; scanf("%d", &t); 
	while (t--) kagari();
	return 0;
}

CF1989E Distance to Different.cpp

Jan 1st, 2026

DP

A 有 n 个数,范围 1~k,且 1~k 每个数至少出现一次。 \(n≤2e5, 2≤k≤10\)
对每一块 \(a_l = a_{l+1} = ... = a_r\),即 \(A[l,r]\) 形如 \(1\ 1\ 1\ 1\ 1\ 1\) 时,\(B[l,r]\)\(1\ 2\ 3\ 3\ 2\ 1\)
给出 n,k,自定义 A,求 B 的可能数量。

发现 A 每个位置的具体值是什么不重要。考虑 DP,设 \(d_{i\ j}\) 为 前 i 个位置,使用了 j 个数后,B 的总数量。

前 i-1 位都已计算后,考虑第 i 位:

  1. 和前一位用一样的数
    $d_{i\ j} += d_{i-1\ j} $

  2. 从某一位起开始使用某个数
    $d_{i\ j} += \Sigma_{p=1}^{i-1} d_{p\ j-1} $

  3. 用一个未使用的数(j+1)
    $d_{i\ j} += \Sigma_{p=1}^{i-1} d_{p\ j-1} $

也就是
$d_{i\ j} = $

这样存在一个问题,形如:A{ Z X Y Z } 时 B{ ... 1 1 ... } 与 A{ Z X X Z } 时 B 是一样的。所以我们要删除非两端,即 i>1&&i<n 的 \(d_{i-2}\)

#include <stdio.h>
#include <algorithm>
#define ll long long 
#define MODN 998244353ll 

int n, k;
ll d[200003][13], s[13];

inline void kagari () {
	scanf("%d %d", &n, &k);
	s[0] = 1;
	for (int i = 1; i <= n; ++i) {
		for (int j = 1; j <= std:: min(k, i); ++j) {
	// ...AAAA -> ...AAAA[A]  or  ...AA.. -> ...AABB[B]
			if (j < k) 	d[i][j] = s[j - 1];
			else 		d[i][j] = (s[j] + s[j - 1]) % MODN;
	// attention: A{ Z X Y Z } -> B{ ... 1 1 ... } == A{ Z X X Z } 
	// So we don't need any square whose len = 2 in the middle of A
			if (i > 1 && i < n && j < k)
				d[i][j] = (d[i][j] + MODN - d[i-2][j-1]) % MODN;
			if (i > 1 && i < n && j == k) 
				d[i][j] = (d[i][j] + MODN + MODN - d[i-2][j-1] - d[i-2][j]) % MODN;
		}
		for (int j = 1; j <= k; ++j) s[j] = (s[j] + d[i][j]) % MODN;
	}
	printf("%lld\n", d[n][k] % MODN);
	return;
}
int main () {
	kagari();
	return 0;
}

CF2187B Shortest Statement Ever

观察题,观察样例,注意到最佳答案 (p, q) 总是可以形如 (x, a) 或 (a, y)。

以 (x, a) 为例,将 a 初始设为 y, 放大或缩小这个 a。四种情况取最佳方案即可。

放大时从 0-29 低位至高位枚举第 k 位是否需放大,若需要则将此位及更低位均设为0,第 k+1 位再加 1,如 \(11101\) 的第三个 1 需调整时,调整为 \(11101 -> 11000, 11000+1000=100000\)

缩小同理,从 29-0 高位至低位枚举第 k 位,若此位要缩小,则此位及其低位一定是形如 \(1....\) 的,最佳方案一定是调整为 \(011...1\),例如 \(11101\) 的情况要删去第二个 1,则变为 \(11000\) 后再减 1,也就是\(10111\)

#include <stdio.h>

int x, y; 

inline void kagari () {
	scanf("%d %d", &x, &y);
	int a, p, q, ma;
	
	a = y;
	for (int k = 0; k <= 29; ++k) 
		if ((x>>k) & (a>>k) & 1) a = (a>>k+1<<k+1) + (1<<k+1);
	p = x, q = a, ma = a - y;
	
	a = y;
	for (int k = 29; ~k; --k) 
		if ((x>>k) & (a>>k) & 1) 
			a = ((a>>k)<<k) - 1;
	if (y - a < ma) p = x, q = a, ma = y - a;
	
	a = x;
	for (int k = 0; k <= 29; ++k) 
		if ((a>>k) & (y>>k) & 1) a = (a>>k+1<<k+1) + (1<<k+1);
	if (a - x < ma) p = a, q = y, ma = a - x;
	
	a = x;
	for (int k = 29; ~k; --k) 
		if ((a>>k) & (y>>k) & 1) 
			a = (a>>k<<k) - 1;
	if (x - a < ma) p = a, q = y, ma = x - a;
	
	printf("%d %d\n", p, q);
	return;
}
int main () {
	int t; scanf("%d", &t); 
	while (t--) kagari();
	return 0;
}

CF2182D Christmas Tree Decoration

贪心的想,a[0] 优先给小的那些。注意到最终一定先运行 l 轮,这时 a 形如 a[0] 1 1 .. 1 0 0 ... 0,若此时 \(a[0]<0\) 则无解。如果 a[0] 还足以再运行一轮,则剩余的 a 形如 a[0] 0 0 ... 0。然后 \(a[0]:=a[0]%n\)

设 t1 为 a 中 1 的数量,答案的组成就是:

  • 在 t1+a[0] 个 1 中排列
  • 在 n-t1-a[0] 个 0 中排列
  • 在 n-t1 个 0 中选取 a[0] 个变为 1

也就是 \((a[0] + t1)! × (n - a[0] - t1)! × C_{n-t1}^{a[0]}\)

注意在计算组合数时使用逆元。

逆元: https://blog.csdn.net/arrowlll/article/details/52629448
Lucas: https://oi-wiki.org/math/number-theory/lucas/

// 逆元: https://blog.csdn.net/arrowlll/article/details/52629448
// Lucas: https://oi-wiki.org/math/number-theory/lucas/

#include <stdio.h>
#include <algorithm>
#define ll long long
#define MODN 998244353ll

int n, a[53];
ll f[1000003];

inline void exgcd (ll a, ll b, ll &x, ll &y) {
	if (!b) x = 1, y = 0;
	else {
		exgcd(b, a % b, y, x);
		y -= a / b * x;
	}
}
inline ll inv (ll a, ll b) {
	ll x, y;
	exgcd(a, b, x, y);
	return (x + b) % b;
}

inline ll C (int n, int k) {
	if (k == 0) return 1;
	if (k > n) return 0;
	return f[n] * inv(f[k], MODN) % MODN * inv(f[n - k], MODN) % MODN;
}

inline void kagari () {
	scanf("%d", &n);
	for (int i = 0; i <= n; ++i) scanf("%d", &a[i]);
	std:: sort(a + 1, a + n + 1);
	for (int i = 1; i < n; ++i) if (a[i] < a[n] - 1) a[0] -= a[n] - 1 - a[i], a[i] = a[n] - 1;
	if (a[0] < 0) { puts("0"); return; }
	int t1 = 0;
	for (int i = 1; i <= n; ++i) if (a[i] == a[n]) a[i] = 1, ++t1; else a[i] = 0;
	if (t1 + a[0] >= n) {
		t1 = 0;
		for (int i = 1; i <= n; ++i) if (!a[i]) --a[0]; else a[i] = 0;
	}
	a[0] %= n;
	printf("%lld\n", f[a[0] + t1] * f[n - a[0] - t1] % MODN * C(n - t1, a[0]) % MODN);
	return;
}
int main () {
	f[0] = f[1] = 1;
	for (ll i = 2; i <= 1000001; ++i) f[i] = f[i - 1] * i % MODN;
	int t; scanf("%d", &t); 
	while (t--) kagari();
	return 0;
}

P12142 [蓝桥杯 2025 省 A] 黑客

我赞赏的题目,主要是逆元和一点点数学内容。

题目大意:已知 \(n×m+2=l+2\),在 \(n×m+2\) 个数中选 2 个为 \(n\)\(m\),其余为 n*m 矩阵中的数,问有多少种矩阵方案,对 1e9+7 取模。

显然我们可以在 \(1\)\(\sqrt{l}\) 间枚举 n,发现 n, m 确定时方案的数量与具体值无关,只与每个数出现的次数有关,果断开桶。设这个桶内的元素为 \(a_1, a_2, ..., a_k\),当前的方案就是\(C_l^{a_1} C_{l-a_1}^{a_2} C_{l-a_1-a_2}^{a_3} ... C_{l-a_1-a_2-...-a_{k-1}}^{a_k}\\=\dfrac{l!}{a_1!a_2!...a_k!}\)
分母的部分用逆元处理。

程序中,每次枚举 n, m 时再在这个数的基础上乘上 \(a_n a_m\)\(a_n(a_n-1)\) 即可。

#include <stdio.h>
#include <algorithm>
#define ll long long
#define MODN 1000000007
#define L 500000

int l, x;
ll a[500003], ans;
ll ft[500003], inv[500003];

inline void exgcd (ll a, ll b, ll &x, ll &y) {
	if (!b) x = 1, y = 0;
	else {
		exgcd(b, a % b, y, x);
		y -= a / b * x;
	}
}
inline ll getinv (ll a, ll b) {
	ll x, y;
	exgcd(a, b, x, y);
	return (x + b) % b;
} 

inline void kagari () {
	scanf("%d", &l); l -= 2;
	for (int i = 1; i <= l + 2; ++i) scanf("%d", &x), ++a[x];
	
	ll q = 1;
	for (int i = 1; i <= L; ++i) if (a[i]) q = q * inv[a[i]] % MODN;
	q = q * ft[l] % MODN;
	for (int n = 1; n * n <= l; ++n) {
		int m = l / n; if (!a[n] || !a[m] || n * m != l || n == m && a[n] <= 1) continue;
		if (n == m) ans += a[n] * (a[n] - 1) * q;
		else ans += a[n] * a[m] * q * 2;
		ans %= MODN;
	}
	printf("%lld\n", ans);
	return;
}
int main () {
	ft[0] = ft[1] = 1, inv[1] = inv[1] = getinv(1, MODN);
	for (ll i = 2; i <= L; ++i) ft[i] = ft[i - 1] * i % MODN, inv[i] = getinv(ft[i], MODN);
	kagari();
	return 0;
}

CF2197A Game with a Fraction

以为是 Bob 先动,想了一个小时都解释不通样例。

设当前为 p, q,轮到 Alice 操作,分类讨论一下

  1. \(\frac{p}{q}=\frac{2}{3}\)
    Bob 直接获胜。
  2. \(\frac{p}{q}<\frac{2}{3}\)
    Alice 选择 p--。对于 Bob,要让 \(\frac{p}{q}\) 大,只能选择 q--;而根据糖水不等式,\(\frac{p-1}{q-1}<\frac{p}{q}\),所以 \(\frac{p}{q}\) 不可能变得更大,Alice 必胜。
  3. \(\frac{p}{q}\geq1\)
    Alice 选择 q--,同理 Bob 选择 p--,\(\frac{p-k}{q-k}\geq1\) 总是成立,Alice 必胜。
  4. \(\frac{2}{3}<\frac{p}{q}<1\)
    无论 Alice 如何选择,Bob 都可以选择相反的操作,使这个分数变为 \(\frac{p-k}{q-k}\)。注意到 \(\frac{p-k}{q-k}=\frac{2}{3}\) 总是有解 \(k=3p-2q\),也就是如此 \(3p-2q\) 轮后一定变成$\frac{2}{3}。所以 Bob 必胜。

以上,\(\frac{2}{3}\leq\frac{p}{q}<1\) 时 Bob wins,否则 Alice wins。

#include <stdio.h>
#define ll long long

ll p, q;
inline ll f () { return (p * 3ll - q * 2ll); }

inline void kagari () {
	scanf("%lld %lld", &p, &q); 
	if (f() == 0 || f() > 0 && q > p) puts("Bob"); 
	else puts("Alice"); 
	return;
}
int main () {
	int t; scanf("%d", &t);
	while (t--) kagari();
	return 0;
}

CF2197B Another Problem about Beautiful Pairs

题目大意:求有多少个 pair \((i, j), i < j\) 使得 \(a_ia_j=j-i\)\(2 \le n \le 2 \cdot 10^{5}\), \(1 \le a_{i} \le 10^{9}\)

注意到 \(a_ia_j=j-i<n\) 恒成立,那么 \(a_i\)\(a_j\) 必定有一个 \(\leq\sqrt{n}\)。枚举 \(i\), 对于每个 \(a_i<n\),再枚举符合条件的 \(a_j\) (这最多枚举 \(\sqrt{n}\) 次)。

  1. \(a_i>\sqrt{n}\) 时,向右找:枚举 \(a_j=\frac{j-i}{a_i}\leq\sqrt{n}\)
  2. 向左找:这时的问题为,已知 \(a_j, j\)\(a_i, i<j\) ,则枚举 \(a_i=\frac{j-i}{a_j}\leq\frac{j-1}{a_j}\) 并满足 \(a_i\leq\sqrt{n}\)\(>\sqrt{n}\)\(a_i\)\(a_i>\sqrt{n}\) 时的情况中已经求过了)。

时间复杂度 \(O(n\sqrt{n})\)

#include <stdio.h>
#include <math.h> 
#define ll long long

int n, a[200003];

inline void kagari () {
	scanf("%d", &n);
	for (int i = 1; i <= n; ++i) scanf("%d", &a[i]); 
	int m = sqrt(n), ans = 0;
	for (int i = 1; i <= n; ++i) if (a[i] > m && a[i] < n) {
		int r = (n-i) / a[i];
		for (int k = 1; k <= r; ++k) 
			if (a[i + k * a[i]] == k) ++ans;
	} 
	for (int i = 1; i <= n; ++i) if (a[i] < n) {
		int r = (i - 1) / a[i]; 
		if (r > m) r = m;
		for (int k = 1; k <= r; ++k) 
			if (a[i - k * a[i]] == k) ++ans;
	} 
	printf("%d\n", ans);
	return;
}
int main () {
	int t; scanf("%d", &t);
	while (t--) kagari();
	return 0;
}

CF2197C2 Interactive Graph

Easy Version 不知道怎么写,因为 Hard Version 方法很显然。

记 d[i] 表示以 i 节点开头的路径数量,由于字典序排序的性质,每当我们遇到某个已经访问过的点 i,都可以跳过 d[i] 次询问。

由于询问 ? 1 时回答一定是 1 1,从 ? 2 开始询问。

程序实现中的 d[i] 意义有所区别。

#include <stdio.h>
#include <vector>

int n, m, d[37], v[37], f[37];
std:: vector < int > g[37];

inline void kagari () {
	scanf("%d", &n); m = 0;
	for (int i = 1; i <= n; ++i) g[i].clear(), d[i] = f[i] = 0;
	int k = 2, maxk = 1073741824; 
	f[1] = 1;
	while (k <= maxk) {
		printf("? %d\n", k);
		fflush(stdout);
		scanf("%d", &v[0]);
		if (v[0] == 0) break;
		for (int p = 1; p <= v[0]; ++p) scanf("%d", &v[p]);
		if (v[0] == 1 && f[v[1]]) {
			int l = v[1];
			while (l <= n && f[l]) k += d[l++] + 1;
			if (l > n) break;
			continue;
		}
		
		if (v[0] > 1) {
			g[v[v[0]-1]].push_back(v[v[0]]);
			++m;
			for (int i = 1; i < v[0]; ++i) d[v[i]]++; 
		}
		k++;
		if (f[v[v[0]]]) k += d[v[v[0]]];
		if (f[v[v[0]]] && v[0] > 1) for (int i = 1; i < v[0]; ++i) d[v[i]] += d[v[v[0]]];
		for (int p = 1; p <= v[0]; ++p) f[v[p]] = 1;
	}
	printf("! %d\n", m);
	for (int i = 1; i <= n; ++i) for (auto &j: g[i]) printf("%d %d\n", i, j);
	fflush(stdout);
	return;
}
int main () {
	int t; scanf("%d", &t);
	while (t--) kagari();
	return 0;
}

P10388 [蓝桥杯 2024 省 A] 团建

题目大意:有两个有根树 A, B,已知每个点的权值,在两个树中,根节点到某个点的路径上,这两个路径的点权相同,求这样的路径最长有几个点。\(n\leq2e5\)

利用树上哈希,用一个大数表示路径: \(hash_i = hash_{father_i} * BASE + a_i\)
dfs 遍历 A 树,同时用 set 存 B 树的 hash 值。对 A 树的某个点 i,存在 B 中某点 j 使得 \(hash_i == hash_j\) 则继续遍历。
注意特判两个树的根节点权值是否相同。

用了一下 double hash。
-> Hash Killer I: https://www.luogu.com.cn/problem/P12197
-> Hash Killer II: https://www.luogu.com.cn/problem/P12198

// Hash of the stree
#include <stdio.h>
#include <vector>
#include <set>
#include <stdlib.h>
#include <time.h>
#define ull unsigned long long
const ull BASE1 = 19491001ull;
ull BASE2 = 255ull;
inline void randbase (auto &b) {
	srand(time(0));
	b = rand();
}

struct TREE {
	int n, a[200003];
	ull hash1[200003], hash2[200003];
	std:: vector < int > g[200003];
	std:: set < ull > s1, s2;
	inline void init () {
		for (int i = 1; i < n; ++i) {
			int x, y; scanf("%d %d", &x, &y);
			g[x].push_back(y), g[y].push_back(x);
		}
		dfs1(1, 0);
	}
	void dfs1 (int i, int frm) {
		hash1[i] = hash1[frm] * BASE1 + a[i],
		hash2[i] = hash2[frm] * BASE2 + a[i];
		s1.insert(hash1[i]), s2.insert(hash2[i]);
		for (auto &j: g[i]) if (j != frm) dfs1(j, i);
	}
} a, b;

int dfs (int i, int frm) {
	int res = 0;
	for (auto &j: a.g[i]) 
	if (j != frm && b.s1.find(a.hash1[j]) != b.s1.end() && b.s2.find(a.hash2[j]) != b.s2.end()) {
		int x = dfs(j, i);
		if (x > res) res = x;
	}
	return res + 1;
}

inline void kagari () {
	scanf("%d %d", &a.n, &b.n);
	for (int i = 1; i <= a.n; ++i) scanf("%d", &a.a[i]);
	for (int i = 1; i <= b.n; ++i) scanf("%d", &b.a[i]);
	a.init(); b.init();
	if (a.a[1] != b.a[1]) { puts("0"); return; }
	int ans = dfs(1, 0);
	printf("%d\n", ans);
	return;
}
int main () {
	randbase(BASE2); 
	kagari();
	return 0;
}

P10391 [蓝桥杯 2024 省 A] 零食采购

注意到 \(c_i\leq20\),容易想到用一个 int 整数来表示状态。树上倍增用 lca 求得答案。

注意计算共同祖先那个点的零食,否则爆零。

看洛谷评论区还可以用分块,利用树上莫队根号解决,确实可以,记录一下。
#include <stdio.h>
#include <vector>
#define base 18

int n, q, a[100003], fa[100003][20], d[100003][20], h[100003];
std:: vector < int > g[100003];

void dfs1 (int i, int frm) {
	fa[i][0] = frm, d[i][0] = (1<<a[i]), h[i] = h[frm] + 1;
	for (int k = 1; k <= base; ++k) 
		fa[i][k] = fa[fa[i][k-1]][k-1],
		d[i][k] = (d[fa[i][k-1]][k-1] | d[i][k-1]);
	for (auto &j: g[i]) if (j != frm) dfs1(j, i);
}

inline int lca (int x, int y) {
	if (x == y) return d[x][0];
	int res = 0;
	for (int k = base; ~k; --k) if (h[fa[x][k]] >= h[y]) res |= d[x][k], x = fa[x][k];
	for (int k = base; ~k; --k) if (h[fa[y][k]] >= h[x]) res |= d[y][k], y = fa[y][k];
	if (x == y) return res | d[x][0];
	for (int k = base; ~k; --k) 
		if (fa[x][k] && fa[x][k] != fa[y][k]) 
			res |= d[x][k], res |= d[y][k], x = fa[x][k], y = fa[y][k];
	res |= d[x][0], res |= d[y][0], x = y = fa[x][0];
	res |= d[x][0];
	return res;
}

inline void kagari () {
	scanf("%d %d", &n, &q);
	for (int i = 1; i <= n; ++i) scanf("%d", &a[i]);
	for (int i = 1; i < n; ++i) { int x, y; scanf("%d %d", &x, &y); g[x].push_back(y), g[y].push_back(x); }
	dfs1(1, 0);
	while (q--) {
		int x, y; scanf("%d %d", &x, &y);
		int t = lca(x, y), ans = 0;
		while (t) ans += (t & 1), t >>= 1;
		printf("%d\n", ans);
	}
	return;
}
int main () {
	kagari();
	return 0;
}
posted @ 2026-01-18 16:18  dbg_8  阅读(15)  评论(0)    收藏  举报