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\) 的处理方式:
-
放入 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 }\) 即可; -
放入 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 位:
-
和前一位用一样的数
$d_{i\ j} += d_{i-1\ j} $ -
从某一位起开始使用某个数
$d_{i\ j} += \Sigma_{p=1}^{i-1} d_{p\ j-1} $ -
用一个未使用的数(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 操作,分类讨论一下
- \(\frac{p}{q}=\frac{2}{3}\)
Bob 直接获胜。 - \(\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 必胜。 - \(\frac{p}{q}\geq1\)
Alice 选择 q--,同理 Bob 选择 p--,\(\frac{p-k}{q-k}\geq1\) 总是成立,Alice 必胜。 - \(\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}\) 次)。
- \(a_i>\sqrt{n}\) 时,向右找:枚举 \(a_j=\frac{j-i}{a_i}\leq\sqrt{n}\)。
- 向左找:这时的问题为,已知 \(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;
}

浙公网安备 33010602011771号