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;
}

唉唉。

posted @ 2026-08-27 21:05  Trent900  阅读(29)  评论(0)    收藏  举报