#P2802. 第3题-最小花费代价
          
                        
                                    
                      
        
              - 
          
          
                      1000ms
            
          
                      Tried: 4
            Accepted: 3
            Difficulty: 6
            
          
          
          
                       所属公司 : 
                              阿里
                                
            
                        
              时间 :2025年4月8日-阿里淘天(开发岗)
                              
                      
          
 
- 
                        算法标签>DFS          
 
第3题-最小花费代价
题解
题面描述
在一棵以节点 1 为根的树上,共有 n 个节点,每个节点 i 初始权值为 ai。小红可以进行如下操作:
- 选择任意节点 v 和一个非负整数 x,
 - 对节点 v 的子树中所有与 v 的距离为偶数的节点 u,执行au←au⊕x
 - 操作的代价为 x。
 
问:最少需要多少总代价,才能将所有节点的权值都变为 0?
思路
- 对树做一次 DFS,以根节点 1 为起点,记录当前节点深度 d。
 - 维护两个累积异或值数组
- cum[0]:表示到当前节点为止,对所有深度为偶数位置所做操作的累积异或
 - cum[1]:表示到当前节点为止,对所有深度为奇数位置所做操作的累积异或
 
 - 访问节点 u 时:
- 设 pp=dmod2
 - 当前节点真实权值为 au⊕cum[pp],记作 vu
 - 为了将其变为 0,需在此节点施加操作 x=vu,并累加代价 res+=x
 - 同时更新 cum[pp]←cum[pp]⊕x
 
 - 继续 DFS 遍历子节点,完成后回溯时需将本次对 cum[pp] 的修改撤销。
 
此法对每个节点只做常数次操作,总时间复杂度 O(n)。
代码分析
- 使用显式栈模拟递归,避免递归深度过深。
 - 每个栈元素包含:节点编号 u、父节点 p、深度 d、阶段标记 stg(入栈前处理或回溯处理)、本次操作值 x。
 - 入栈阶段(stg=0)计算需操作值 x 并更新累积异或,出栈阶段(stg=1)撤销更新。
 
C++
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
int main(){
    ios::sync_with_stdio(false);
    cin.tie(NULL);
    int n;
    cin >> n;
    vector<ll> a(n+1);
    for(int i = 1; i <= n; i++) cin >> a[i];
    vector<vector<int>> g(n+1);
    for(int i = 1, u, v; i < n; i++){
        cin >> u >> v;
        g[u].push_back(v);
        g[v].push_back(u);
    }
    ll res = 0;               // 最终总代价
    ll cum[2] = {0, 0};       // cum[0] 偶深度异或累积, cum[1] 奇深度异或累积
    struct Node {
        int u, p, d, stg;
        ll x;
    };
    vector<Node> stk;
    stk.reserve(2*n);
    stk.push_back({1, 0, 0, 0, 0});  // 从根节点入栈,深度0,阶段0
    while(!stk.empty()){
        auto cur = stk.back(); stk.pop_back();
        int u = cur.u, p = cur.p, d = cur.d, stg = cur.stg;
        ll x = cur.x;
        if(stg == 0){
            int pp = d & 1;
            // 计算当前真实值,需要补异或到0的值
            ll need = cum[pp] ^ a[u];
            res += need;
            cum[pp] ^= need;  // 更新累积异或
            // 先入回溯阶段,再入子节点
            stk.push_back({u, p, d, 1, need});
            for(int v : g[u]){
                if(v == p) continue;
                stk.push_back({v, u, d+1, 0, 0});
            }
        } else {
            // 回溯时撤销本节点对 cum 的修改
            int pp = d & 1;
            cum[pp] ^= x;
        }
    }
    cout << res;
    return 0;
}
Python
import sys
sys.setrecursionlimit(10**7)
def solve():
    n = int(sys.stdin.readline())
    a = [0] + list(map(int, sys.stdin.readline().split()))
    g = [[] for _ in range(n+1)]
    for _ in range(n-1):
        u, v = map(int, sys.stdin.readline().split())
        g[u].append(v)
        g[v].append(u)
    res = 0
    cum = [0, 0]  # cum[0] 偶深度异或累积, cum[1] 奇深度异或累积
    # 栈元素: (u, parent, depth, stage, x)
    stk = [(1, 0, 0, 0, 0)]
    while stk:
        u, p, d, stage, x = stk.pop()
        if stage == 0:
            pp = d & 1
            # 计算需要异或的值
            need = cum[pp] ^ a[u]
            res += need
            cum[pp] ^= need
            # 回溯阶段
            stk.append((u, p, d, 1, need))
            for v in g[u]:
                if v != p:
                    stk.append((v, u, d+1, 0, 0))
        else:
            # 撤销
            pp = d & 1
            cum[pp] ^= x
    print(res)
if __name__ == "__main__":
    solve()
Java
import java.io.*;
import java.util.*;
public class Main {
    static int n;
    static long[] a;
    static List<Integer>[] g;
    public static void main(String[] args) throws IOException {
        BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
        n = Integer.parseInt(in.readLine());
        a = new long[n+1];
        StringTokenizer st = new StringTokenizer(in.readLine());
        for(int i = 1; i <= n; i++) {
            a[i] = Long.parseLong(st.nextToken());
        }
        g = new ArrayList[n+1];
        for(int i = 1; i <= n; i++) {
            g[i] = new ArrayList<>();
        }
        for(int i = 0; i < n-1; i++) {
            st = new StringTokenizer(in.readLine());
            int u = Integer.parseInt(st.nextToken());
            int v = Integer.parseInt(st.nextToken());
            g[u].add(v);
            g[v].add(u);
        }
        long res = 0;
        long[] cum = new long[2]; // cum[0] 偶深度, cum[1] 奇深度
        // 栈元素:u, parent, depth, stage, x
        Deque<long[]> stk = new ArrayDeque<>();
        stk.push(new long[]{1, 0, 0, 0, 0});
        while(!stk.isEmpty()) {
            long[] cur = stk.pop();
            int u = (int)cur[0], p = (int)cur[1], d = (int)cur[2];
            int stage = (int)cur[3];
            long x = cur[4];
            if(stage == 0) {
                int pp = d & 1;
                long need = cum[pp] ^ a[u];
                res += need;
                cum[pp] ^= need;
                // 回溯阶段
                stk.push(new long[]{u, p, d, 1, need});
                for(int v : g[u]) {
                    if(v == p) continue;
                    stk.push(new long[]{v, u, d+1, 0, 0});
                }
            } else {
                int pp = d & 1;
                cum[pp] ^= x; // 撤销
            }
        }
        System.out.println(res);
    }
}
        题目内容
在神秘莫测的比特王国中,流传着这样一个传说—在王国深处生长着一棵蕴含天机的神奇大树。这棵树由n个节点构成,1号点是根节点,
每个节点都记录着一段古老的符文,初始每个节点的符文为ai。传说只有将树上所有节点的符文调和为零,才能开启通往失落宝藏的大门,聪明勇敢的小红接受了挑战,她掌握了一套奇特的操作法则,能够对树中部分节点施法改变其符文,但每次施法都需要付出一定代价,
具体来说,小红可以进行如下操作:
- 选择树中的任一节点v及一个非负整数x。
 - 将v 以及v的子树中所有与v距离为偶数的节点的权值进行异或操作,即更新为:a[u]=a[u]⊕x
 - 每进行一次此操作就产生的代价。
 
请问小红至少需要花费多少总代价,才能使得所有节点的符文权值均变为0?
名词解释
在一棵树中,对于任一节点v,以v为根节点并包含所有从出发可以达到的后代节点(包括v自身)的集合构成了v的子树。
在树中,任意两个节点之间仅存在一条简单路径,该路径上所经过的边的数目定义为这两个节点之间的距离。
输入描述
第一行包含一个整数n,表示节点的数量.
第二行包含n个整数a1,a2,...an,表示各节点的初始权值。
接下来n−1行,每行包含两个整数u和v,表示树中一你边。所始边构成一棵以1为根的树。
1≤n≤2×105
0≦a≦109
输出描述
输出一个整数,表示将所有节点的权值变为0所需的最小总代价.
样例1
输入
5
1 4 3 5 5
1 2
2 3
3 4
3 5
输出
9
说明
先选择节点1,花费1的代价进行操作,操作后节点权值变为[0,4,2,5,5]
再选择节点2,花费4的代价进行操作,操作后节点权值变为[0,0,2,1,1]
再选择节点3,花费2的代价进行操作,操作后节点权值变为[0,0,0,1,1]
再选择节点4,花费1的代价进行操作,操作后节点权值变为[0,0,0,0,1]
最后选择节点5,花费1的代价进行操作,操作后节点权值变为[0,0,0,0,0]
总花费为1+4+2+1+1=9