P9745 「KDOI-06-S」树上异或 解题报告
灵活的套路题。
题意就是,有一个带点权的树。定义一种划分方案的得分为每个连通块中点的异或和之积。求所有划分方案的得分之和。
部分分的提示其实很明显:DP + 拆位。但是咋做呢?
我一开始的想法是对每一位单独计算贡献最后相乘,但是这样算会漏掉跨位的乘积,非常不好。
于是不能跨位算,我们先设计 \(f(u, i)\) 代表子树 \(u\) 内(\(u\) 尚未被划分),包含 \(u\) 的连通块异或和为 \(i\),除了这个连通块之外的所有划分方案的答案。
然后子树合并时,我们有
\[f(u, i \oplus j) \leftarrow f(u, i) \cdot f(v, j)
\]
为啥呢:考虑 \(f(u, i)\) 内的所有得分集合为 \((x_1, x_2, ... , x_k)\),\(f(v, j)\) 内的所有得分集合为 \((y_1, y_2, ..., y_l)\)。那么最终的结果其实就是 \((x_1y_1, x_1, y_2, ... , x_2y_1, x_2y_2, ... , x_ky_1, x_ky_2, ..., x_ky_l)\)。其实总和就是二者相乘。
然后因为是异或,所以
\[\sum f(i, j) \cdot j = \sum f(i, j) \cdot (\sum_{k = 0}^{63} 2^kj_k)
\]
其中 \(j_k\) 代表 \(j\) 在二进制下的第 \(k\) 位,因为异或时每一位互不干扰,所以拆位,这个式子等于
\[\sum_{k = 0}^{63} 2^k (\sum g_k(i, j))
\]
\(g_k(i, j)\) 代表子树 \(i\) 中,包含点 \(i\) 的连通块二进制下第 \(k\) 位为 \(j\) 的 \(f\) 值。
此时我们将 \(dp_i\) 定义为 \(\sum j \cdot f(i, j)\)。
那么考虑转移:
\[g_k(i, j) = g_k(i, j) \cdot dp_v + g_k(i, j) \cdot g_k(v, j \oplus {x_i}_k)
\]
\[dp_i = \sum_k 2^j \cdot g_k(i, 1)
\]
答案是 \(dp_1\),做完了。
因为一开始直接想到拆位了然后想歪了,所以这种顺序也是要斟酌的。
#include <bits/stdc++.h>
#define int long long
#define Misaka namespace
#define Network std
using Misaka Network;
const int N = 5e5 + 7, MOD = 998244353;
int n, x[N], dp[N], f[N][60][2], fa[N];
signed main(){
ios::sync_with_stdio(0), cin.tie(0);
cin >> n;
for(int i = 1; i <= n; i ++) cin >> x[i];
for(int i = 2; i <= n; i ++){ cin >> fa[i]; }
for(int i = 1; i <= n; i ++)
for(int b = 0; b < 60; b ++)
f[i][b][(x[i] >> b) & 1] = 1;
for(int u = n; u >= 1; u --){
for(int b = 0; b < 60; b ++)
dp[u] = (dp[u] + f[u][b][1] * ((1ll << b) % MOD)) % MOD;
if(u > 1){
int p = fa[u];
for(int b = 0; b < 60; b ++){
int a0 = f[p][b][0], a1 = f[p][b][1];
int v0 = f[u][b][0], v1 = f[u][b][1];
f[p][b][0] = (a0 * dp[u] + a0 * v0 + a1 * v1) % MOD;
f[p][b][1] = (a1 * dp[u] + a0 * v1 + a1 * v0) % MOD;
}
}
}
cout << dp[1] << "\n";
return 0;
}
唉唉。

浙公网安备 33010602011771号