翻转树上最少边

Posted by farmer3-c on January 19, 2026

翻转树上最少边

给你一棵包含 n 个节点的 无向树,节点编号从 0 到 n - 1。该树由长度为 n - 1 的二维整数数组 edges 表示,其中 edges[i] = [ai, bi] 表示树中节点 ai 和 bi 之间存在一条边。

另外给你两个长度为 n 的 二进制 字符串 start 和 target。对于每个节点 x,start[x] 是其初始颜色,而 target[x] 是其目标颜色。

在一次操作中,你可以选择下标为 i 的一条边并 翻转 它的两个端点。也就是说,如果这条边是 [u, v],那么节点 u 和 v 的颜色 各自 从 ‘0’ 变为 ‘1’,或者从 ‘1’ 变为 ‘0’。

返回一个边下标数组,执行这些边对应的操作可以将 start 转换为 target。在所有有效序列中找出 长度最短 的序列,以 升序 返回边下标。

如果无法将 start 转换为 target,则返回一个仅包含单个元素 -1 的数组。

思路

树的题目一般都可以用dfs来解决。

方法一:自底向上的 DFS

把问题看成: 每条边最多翻转一次,翻转后会影响这条边两端节点的颜色。对于每个节点,只关心它“最终是否需要被翻转奇数次”。

在树上做 DFS 后序遍历

先处理子树

再决定是否通过“连接父亲的那条边”来修正当前节点

叶子节点的颜色如果与目标颜色不同,那么就一定要翻转和它相连接的两个节点,同时将边加入答案。移除原来的叶子节点,对新的叶子节点进行同样的操作。

点击展开/折叠代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
class Solution {
public:
    vector<int> minimumFlips(int n, vector<vector<int>>& edges, string start, string target) {
        vector<vector<pair<int,int>>> g(n);
        for(int i=0;i<n-1;i++){
            int x=edges[i][0],y=edges[i][1];
            g[x].emplace_back(y,i);
            g[y].emplace_back(x,i);
        }
        vector<int> ans;
       std:: function <bool(int,int)> dfs=[&](int x,int fa)->bool{
            bool rev=start[x]!=target[x];
            for(auto& [y,i]:g[x]){
                if(y!=fa&&dfs(y,x)){
                    ans.push_back(i);
                    rev=!rev;
                }
            }
            return rev;
        };
        if(dfs(0,-1)){
            return {-1};
        }
        sort(ans.begin(),ans.end());
        return ans;
    }
};

正确性说明

1
2
3
4
5
6
7
8
9
树结构保证:

    每个节点(除根)只有一条父边可以“补救”它

贪心是最优的:

    子节点问题必须在子树处理完后才能处理

    父边是唯一能影响子节点的外部操作

时间复杂度:O(n),其中n为节点个数。 空间复杂度:O(n),其中n为节点个数。 排序答案:O(n log n)

方法二:树上“异或差分”思想(等价但更直观)

核心思想

1
2
3
4
5
6
7
8
9
10
把每个节点需要的变化看成一个值:

need[x] = (start[x] != target[x]) ? 1 : 0;


翻转一条边 (u, v):

相当于:need[u] ^= 1, need[v] ^= 1

目标:通过选一些边,使得所有 need[x] = 0

树上贪心策略

1
2
3
4
5
6
7
8
9
10
11
12
从叶子向根 DFS

如果某个子节点 y 的 need[y] == 1

那么必须翻转父边 (x, y)

否则 y 永远无法变成 0

翻转后:

need[y] = 0;
need[x] ^= 1;

算法流程

1
2
3
4
5
6
7
DFS 返回后,子节点要么是 0,要么已经通过父边解决

最终检查根节点:

need[root] == 0 → 可行

否则 → 无解
点击展开/折叠代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
class Solution {
public:
    vector<int> minimumFlips(int n, vector<vector<int>>& edges, string start, string target) {
        vector<vector<pair<int,int>>> g(n);
        for(int i=0;i<n-1;i++){
            int u = edges[i][0], v = edges[i][1];
            g[u].push_back({v,i});
            g[v].push_back({u,i});
        }

        vector<int> need(n);
        for(int i=0;i<n;i++){
            need[i] = (start[i] != target[i]);
        }

        vector<int> ans;

        auto dfs = [&](this auto&& dfs, int x, int fa)->void {
            for(auto& [y, id] : g[x]){
                if(y == fa) continue;
                dfs(y, x);
                if(need[y]){
                    ans.push_back(id);
                    need[y] = 0;
                    need[x] ^= 1;
                }
            }
        };

        dfs(0, -1);
        if(need[0]) return {-1};

        sort(ans.begin(), ans.end());
        return ans;
    }
};

参考