[题解] 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)\) 的状态方程即为:

\[f(u) = f(v) + size_v \times (a_u \oplus a_v) \]

对于 \(g(x)\) ,我们使用换根 dp 经典的计算方式即可。

\[g(v) = f(u) - f(v) - size_v \times (a_u \oplus a_v) + g(u) + (n - size_v) \times (a_u \oplus a_v) \]

最后计算结果,\(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];
    }
}
posted @ 2023-09-27 20:30  フランドール·スカーレット  阅读(74)  评论(0)    收藏  举报