[题解] CF1882D - Tree XOR
CF1882D - Tree XOR
知识点:换根 DP 。
主要难点是要思考如何操作使得代价最小,这个过程是一个贪心的过程。想到怎么操作,计算答案的过程就是一个板子换根了。
题意
给定一颗 \(n\) 个节点的树,点 \(i\) 具有权值 \(a_i\) 。现在需要你不断执行以下操作,使得树上所有点的权值相等。
- 记当前根节点为 \(root\) ,一次操作中,可以选择一个点 \(v\) 和任意整数 \(c\) ,将 \(v\) 子树内所有节点的权值异或 \(c\) ,代价为一次异或的节点数量。
问当根节点为 \(root = 1, 2, \cdots , n\) 时,使得树上所有节点权值相等的最小代价为多少。
思路
假设我们现在选择一个点 \(v\) ,我们要对其进行操作。如果其子树内存在节点,权值不等于 \(a_v\) ,那么意味着我们无论怎么操作,都不可能使得树上所有点相等。
因此,如果如此递归考虑,那么我们每次执行操作,必然是从叶子节点到根节点,每次操作都是让当前节点 \(u\) 子树的所有节点权值改为 \(a_u\) ,直到最后的根节点,这样代价才能达到最小。
设 \(v\) 是 \(u\) 的儿子节点,那么按照我们上述所说的思路,这次操作的代价应该为 \(cost_v + size_v \times (a_u \oplus a_v)\) ,其中 \(cost_v\) 是将 \(v\) 的所有子树节点权值更改为 \(a_v\) 的代价。
但是,我们现在考虑的代价只是以一个根节点进行考虑的,并不是 \(n\) 个根节点轮流的情况,此时我们可以考虑,在计算了一个根节点的情况上,计算这个节点是根节点的代价,也就是换根 dp 。
设 \(f(x) = cost_x\) ,表示在根节点为 \(root\) 的情况下(不妨设 \(root = 1\) ),将 \(x\) 的所有子树节点权值改为 \(a_x\) 的代价; \(g(x)\) 为把除了 \(x\) 的子树以外的节点权值改为 \(a_x\) 的代价。那么,\(f(x)\) 的状态方程即为:
对于 \(g(x)\) ,我们使用换根 dp 经典的计算方式即可。
最后计算结果,\(ans(x) = f(x) + g(x)\) 。
实现
auto Main() -> void {
int n;
std::cin >> n;
std::vector<i64> vw(n);
for (auto &x : vw) {
std::cin >> x;
}
std::vector adj(n, std::vector<int>{});
for (int i = 0; i < n - 1; i ++) {
int u, v;
std::cin >> u >> v;
u --; v --;
adj[u].emplace_back(v);
adj[v].emplace_back(u);
}
std::vector<int> siz(n);
std::vector<i64> dp1(n), dp2(n);
auto dfs1 = [&](auto &self, int from, int come) -> void {
siz[from] = 1;
for (auto to : adj[from]) {
if (to == come) {
continue;
}
self(self, to, from);
siz[from] += siz[to];
dp1[from] += dp1[to] + (i64(vw[from] ^ vw[to]) * siz[to]);
}
};
dfs1(dfs1, 0, -1);
auto dfs2 = [&](auto &self, int from, int come) -> void {
if (come != -1) {
dp2[from] = dp1[come] - dp1[from] - (i64(vw[come] ^ vw[from]) * siz[from]) + dp2[come] + (i64(vw[from] ^ vw[come]) * (n - siz[from]));
}
for (auto to : adj[from]) {
if (to != come) {
self(self, to, from);
}
}
};
dfs2(dfs2, 0, -1);
for (int i = 0; i < n; i ++) {
std::cout << dp1[i] + dp2[i] << " \n"[i + 1 == n];
}
}

浙公网安备 33010602011771号