跳转至

2581. 统计可能的树根数目

来源第 99 场双周赛 Q4难度困难分数2228

题目描述

Alice 有一棵 n 个节点的树,节点编号为 0 到 n - 1 。树用一个长度为 n - 1 的二维整数数组 edges 表示,其中 edges[i] = [ai, bi] ,表示树中节点 ai 和 bi 之间有一条边。

Alice 想要 Bob 找到这棵树的根。她允许 Bob 对这棵树进行若干次 猜测 。每一次猜测,Bob 做如下事情:

  • 选择两个 不相等 的整数 u 和 v ,且树中必须存在边 [u, v] 。
  • Bob 猜测树中 u 是 v 的 父节点 。

Bob 的猜测用二维整数数组 guesses 表示,其中 guesses[j] = [uj, vj] 表示 Bob 猜 uj 是 vj 的父节点。

Alice 非常懒,她不想逐个回答 Bob 的猜测,只告诉 Bob 这些猜测里面 至少 有 k 个猜测的结果为 true 。

给你二维整数数组 edges ,Bob 的所有猜测和整数 k ,请你返回可能成为树根的 节点数目 。如果没有这样的树,则返回 0。

 

示例 1:

输入:edges = [[0,1],[1,2],[1,3],[4,2]], guesses = [[1,3],[0,1],[1,0],[2,4]], k = 3
输出:3
解释:
根为节点 0 ,正确的猜测为 [1,3], [0,1], [2,4]
根为节点 1 ,正确的猜测为 [1,3], [1,0], [2,4]
根为节点 2 ,正确的猜测为 [1,3], [1,0], [2,4]
根为节点 3 ,正确的猜测为 [1,0], [2,4]
根为节点 4 ,正确的猜测为 [1,3], [1,0]
节点 0 ,1 或 2 为根时,可以得到 3 个正确的猜测。

示例 2:

输入:edges = [[0,1],[1,2],[2,3],[3,4]], guesses = [[1,0],[3,4],[2,1],[3,2]], k = 1
输出:5
解释:
根为节点 0 ,正确的猜测为 [3,4]
根为节点 1 ,正确的猜测为 [1,0], [3,4]
根为节点 2 ,正确的猜测为 [1,0], [2,1], [3,4]
根为节点 3 ,正确的猜测为 [1,0], [2,1], [3,2], [3,4]
根为节点 4 ,正确的猜测为 [1,0], [2,1], [3,2]
任何节点为根,都至少有 1 个正确的猜测。

 

提示:

  • edges.length == n - 1
  • 2 <= n <= 105
  • 1 <= guesses.length <= 105
  • 0 <= ai, bi, uj, vj <= n - 1
  • ai != bi
  • uj != vj
  • edges 表示一棵有效的树。
  • guesses[j] 是树中的一条边。
  • guesses 是唯一的。
  • 0 <= k <= guesses.length

解法

方法一:树形 DP(换根)

思考

树的根未知,猜测是有向边。若以某点为根时猜中不少于 \(k\) 条,则该点可行。对每个根单独 DFS 需 \(O(n^2)\)。

先以 \(0\) 为根统计猜中数 \(\textit{cnt}\)。换根到邻点 \(j\) 时,边 \((i,j)\) 不再由父指向子, \((j,i)\) 则变为由父指向子,故 \(\textit{cnt}\) 增减对应猜测是否存在。第二次 DFS 遍历每个根并计数。

我们先遍历题目给定的边集合 \(\textit{edges}\),将其转换为邻接表 \(g\),其中 \(g[i]\) 表示节点 \(i\) 的所有邻接节点。用哈希表 \(gs\) 记录题目给定的猜测集合 \(\textit{guesses}\)。

接下来,我们先从节点 \(0\) 开始,进行一次 DFS,统计从节点 \(0\) 出发,能够到达的所有节点中,有多少条边在 \(\textit{guesses}\) 中。我们用变量 \(\textit{cnt}\) 记录这个数量。

然后,我们再从节点 \(0\) 开始,进行一次 DFS,统计以每个点为根的树中,有多少条边在 \(\textit{guesses}\) 中。如果这个数量大于等于 \(k\),则说明该节点是一个可能的根节点,我们将答案加 \(1\)。

因此,问题就转化为了求以每个点为根的树中,有多少条边在 \(\textit{guesses}\) 中。我们已经知道了从节点 \(0\) 出发,能够到达的所有节点中,有多少条边在 \(\textit{guesses}\) 中,即 \(\textit{cnt}\)。我们可以通过在 DFS 中,将 \(\textit{cnt}\) 的值加上或减去当前边是否在 \(\textit{guesses}\) 中,来维护这个值。

假设我们当前遍历到节点 \(i\),此时 \(\textit{cnt}\) 表示以 \(i\) 为根节点,有多少条边在 \(\textit{guesses}\) 中。那么对于 \(i\) 的每个邻接节点 \(j\),我们要计算以 \(j\) 为根节点,有多少条边在 \(\textit{guesses}\) 中。如果 \((i, j)\) 在 \(\textit{guesses}\) 中,那么以 \(j\) 为根节点的,就不存在 \((i, j)\) 这条边,因此 \(\textit{cnt}\) 的值要减去 \(1\)。如果 \((j, i)\) 在 \(\textit{guesses}\) 中,那么以 \(j\) 为根节点的,要多出一条 \((i, j)\) 这条边,因此 \(\textit{cnt}\) 的值要加上 \(1\)。即 \(f[j] = f[i] + (j, i) \in \textit{guesses} - (i, j) \in \textit{guesses}\)。其中 \(f[i]\) 表示以 \(i\) 为根节点,有多少条边在 \(\textit{guesses}\) 中。

时间复杂度 \(O(n + m)\),空间复杂度 \(O(n + m)\)。其中 \(n\) 和 \(m\) 分别为 \(\textit{edges}\) 和 \(\textit{guesses}\) 的长度。

相似题目:

 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
class Solution:
    def rootCount(
        self, edges: List[List[int]], guesses: List[List[int]], k: int
    ) -> int:
        def dfs1(i, fa):
            nonlocal cnt
            for j in g[i]:
                if j != fa:
                    cnt += gs[(i, j)]
                    dfs1(j, i)

        def dfs2(i, fa):
            nonlocal ans, cnt
            ans += cnt >= k
            for j in g[i]:
                if j != fa:
                    cnt -= gs[(i, j)]
                    cnt += gs[(j, i)]
                    dfs2(j, i)
                    cnt -= gs[(j, i)]
                    cnt += gs[(i, j)]

        g = defaultdict(list)
        for a, b in edges:
            g[a].append(b)
            g[b].append(a)
        gs = Counter((u, v) for u, v in guesses)
        cnt = 0
        dfs1(0, -1)
        ans = 0
        dfs2(0, -1)
        return ans
 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
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
class Solution {
    private List<Integer>[] g;
    private Map<Long, Integer> gs = new HashMap<>();
    private int ans;
    private int k;
    private int cnt;
    private int n;

    public int rootCount(int[][] edges, int[][] guesses, int k) {
        this.k = k;
        n = edges.length + 1;
        g = new List[n];
        Arrays.setAll(g, e -> new ArrayList<>());
        for (var e : edges) {
            int a = e[0], b = e[1];
            g[a].add(b);
            g[b].add(a);
        }
        for (var e : guesses) {
            int a = e[0], b = e[1];
            gs.merge(f(a, b), 1, Integer::sum);
        }
        dfs1(0, -1);
        dfs2(0, -1);
        return ans;
    }

    private void dfs1(int i, int fa) {
        for (int j : g[i]) {
            if (j != fa) {
                cnt += gs.getOrDefault(f(i, j), 0);
                dfs1(j, i);
            }
        }
    }

    private void dfs2(int i, int fa) {
        ans += cnt >= k ? 1 : 0;
        for (int j : g[i]) {
            if (j != fa) {
                int a = gs.getOrDefault(f(i, j), 0);
                int b = gs.getOrDefault(f(j, i), 0);
                cnt -= a;
                cnt += b;
                dfs2(j, i);
                cnt -= b;
                cnt += a;
            }
        }
    }

    private long f(int i, int j) {
        return 1L * i * n + j;
    }
}
 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
38
39
40
41
42
43
44
45
46
47
48
49
class Solution {
public:
    int rootCount(vector<vector<int>>& edges, vector<vector<int>>& guesses, int k) {
        int n = edges.size() + 1;
        vector<int> g[n];
        unordered_map<long long, int> gs;
        auto f = [&](int i, int j) {
            return 1LL * i * n + j;
        };
        for (auto& e : edges) {
            int a = e[0], b = e[1];
            g[a].push_back(b);
            g[b].push_back(a);
        }
        for (auto& e : guesses) {
            int a = e[0], b = e[1];
            gs[f(a, b)]++;
        }
        int ans = 0;
        int cnt = 0;

        function<void(int, int)> dfs1 = [&](int i, int fa) {
            for (int& j : g[i]) {
                if (j != fa) {
                    cnt += gs[f(i, j)];
                    dfs1(j, i);
                }
            }
        };

        function<void(int, int)> dfs2 = [&](int i, int fa) {
            ans += cnt >= k;
            for (int& j : g[i]) {
                if (j != fa) {
                    int a = gs[f(i, j)];
                    int b = gs[f(j, i)];
                    cnt -= a;
                    cnt += b;
                    dfs2(j, i);
                    cnt -= b;
                    cnt += a;
                }
            }
        };
        dfs1(0, -1);
        dfs2(0, -1);
        return ans;
    }
};
 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
38
39
40
41
42
43
44
45
46
47
func rootCount(edges [][]int, guesses [][]int, k int) (ans int) {
    n := len(edges) + 1
    g := make([][]int, n)
    gs := map[int]int{}
    for _, e := range edges {
        a, b := e[0], e[1]
        g[a] = append(g[a], b)
        g[b] = append(g[b], a)
    }
    f := func(i, j int) int {
        return i*n + j
    }
    for _, e := range guesses {
        a, b := e[0], e[1]
        gs[f(a, b)]++
    }

    cnt := 0
    var dfs1 func(i, fa int)
    var dfs2 func(i, fa int)
    dfs1 = func(i, fa int) {
        for _, j := range g[i] {
            if j != fa {
                cnt += gs[f(i, j)]
                dfs1(j, i)
            }
        }
    }
    dfs2 = func(i, fa int) {
        if cnt >= k {
            ans++
        }
        for _, j := range g[i] {
            if j != fa {
                a, b := gs[f(i, j)], gs[f(j, i)]
                cnt -= a
                cnt += b
                dfs2(j, i)
                cnt -= b
                cnt += a
            }
        }
    }
    dfs1(0, -1)
    dfs2(0, -1)
    return
}
 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
38
39
40
41
function rootCount(edges: number[][], guesses: number[][], k: number): number {
    const n = edges.length + 1;
    const g: number[][] = Array.from({ length: n }, () => []);
    const gs: Map<number, number> = new Map();
    const f = (i: number, j: number) => i * n + j;
    for (const [a, b] of edges) {
        g[a].push(b);
        g[b].push(a);
    }
    for (const [a, b] of guesses) {
        const x = f(a, b);
        gs.set(x, gs.has(x) ? gs.get(x)! + 1 : 1);
    }
    let ans = 0;
    let cnt = 0;
    const dfs1 = (i: number, fa: number): void => {
        for (const j of g[i]) {
            if (j !== fa) {
                cnt += gs.get(f(i, j)) || 0;
                dfs1(j, i);
            }
        }
    };
    const dfs2 = (i: number, fa: number): void => {
        ans += cnt >= k ? 1 : 0;
        for (const j of g[i]) {
            if (j !== fa) {
                const a = gs.get(f(i, j)) || 0;
                const b = gs.get(f(j, i)) || 0;
                cnt -= a;
                cnt += b;
                dfs2(j, i);
                cnt -= b;
                cnt += a;
            }
        }
    };
    dfs1(0, -1);
    dfs2(0, -1);
    return ans;
}

方法二:换根 DP + 显式栈

思考

树的根未知,每条猜测是一条有向边。以某点为根时,猜中条数不少于 \(k\) 才算可行。 \(n\le 10^5\),对每个根各遍历一次是平方级;沿链递归时调用深度等于节点数,链长达到 \(1000\) 就会超出 Python 的递归上限。猜中数在换根时只变一条边:从父亲 \(i\) 走到孩子 \(j\), \((i,j)\) 不再由父指向子, \((j,i)\) 变为由父指向子。第一遍用栈从 \(0\) 累加方向与父子关系一致的猜测,得到 \(\textit{cnt}\)。第二遍把调整后的计数放进栈帧,弹出时若计数不少于 \(k\) 就记一个可行根,再把 \(\textit{cnt}-(i,j)+(j,i)\) 传给孩子。

先把 \(\textit{edges}\) 转成邻接表 \(g\),并用哈希表 \(gs\) 记录 \(\textit{guesses}\)。用显式栈从节点 \(0\) 遍历整棵树。走到孩子 \(j\) 时,若 \((i,j)\) 在 \(gs\) 中,就把 \(\textit{cnt}\) 加上对应次数。遍历结束后, \(\textit{cnt}\) 就是以 \(0\) 为根时猜中的条数。

再换根。栈里存放节点、父亲和以该节点为根时的猜中数。初始帧是 \((0,-1,\textit{cnt})\)。弹出节点 \(i\) 时,若当前计数不少于 \(k\),答案加 \(1\)。对于邻居 \(j\),以 \(j\) 为根时 \((i,j)\) 不再由父指向子, \((j,i)\) 变为由父指向子,所以传给孩子的计数是 \(c-gs[(i,j)]+gs[(j,i)]\)。

时间复杂度 \(O(n + m)\),空间复杂度 \(O(n + m)\)。其中 \(n\) 和 \(m\) 分别为 \(\textit{edges}\) 和 \(\textit{guesses}\) 的长度。

相似题目:

 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
class Solution:
    def rootCount(
        self, edges: List[List[int]], guesses: List[List[int]], k: int
    ) -> int:
        g = defaultdict(list)
        for a, b in edges:
            g[a].append(b)
            g[b].append(a)
        gs = Counter((u, v) for u, v in guesses)
        cnt = 0
        stk = [(0, -1)]
        while stk:
            i, fa = stk.pop()
            for j in g[i]:
                if j != fa:
                    cnt += gs[(i, j)]
                    stk.append((j, i))
        ans = 0
        walk = [(0, -1, cnt)]
        while walk:
            i, fa, c = walk.pop()
            ans += c >= k
            for j in g[i]:
                if j != fa:
                    walk.append((j, i, c - gs[(i, j)] + gs[(j, i)]))
        return ans
 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
38
39
40
41
42
43
44
45
46
47
class Solution {
    public int rootCount(int[][] edges, int[][] guesses, int k) {
        int n = edges.length + 1;
        List<Integer>[] g = new List[n];
        Arrays.setAll(g, e -> new ArrayList<>());
        for (var e : edges) {
            int a = e[0], b = e[1];
            g[a].add(b);
            g[b].add(a);
        }
        Map<Long, Integer> gs = new HashMap<>();
        for (var e : guesses) {
            gs.merge(1L * e[0] * n + e[1], 1, Integer::sum);
        }
        int cnt = 0;
        Deque<int[]> stk = new ArrayDeque<>();
        stk.push(new int[] {0, -1});
        while (!stk.isEmpty()) {
            int[] cur = stk.pop();
            int i = cur[0], fa = cur[1];
            for (int j : g[i]) {
                if (j != fa) {
                    cnt += gs.getOrDefault(1L * i * n + j, 0);
                    stk.push(new int[] {j, i});
                }
            }
        }
        int ans = 0;
        Deque<int[]> walk = new ArrayDeque<>();
        walk.push(new int[] {0, -1, cnt});
        while (!walk.isEmpty()) {
            int[] cur = walk.pop();
            int i = cur[0], fa = cur[1], c = cur[2];
            if (c >= k) {
                ++ans;
            }
            for (int j : g[i]) {
                if (j != fa) {
                    int a = gs.getOrDefault(1L * i * n + j, 0);
                    int b = gs.getOrDefault(1L * j * n + i, 0);
                    walk.push(new int[] {j, i, c - a + b});
                }
            }
        }
        return ans;
    }
}
 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
38
39
40
41
42
43
44
45
class Solution {
public:
    int rootCount(vector<vector<int>>& edges, vector<vector<int>>& guesses, int k) {
        int n = edges.size() + 1;
        vector<vector<int>> g(n);
        for (auto& e : edges) {
            int a = e[0], b = e[1];
            g[a].push_back(b);
            g[b].push_back(a);
        }
        unordered_map<long long, int> gs;
        for (auto& e : guesses) {
            gs[1LL * e[0] * n + e[1]]++;
        }
        auto get = [&](int i, int j) {
            auto it = gs.find(1LL * i * n + j);
            return it == gs.end() ? 0 : it->second;
        };
        int cnt = 0;
        vector<pair<int, int>> stk{{0, -1}};
        while (!stk.empty()) {
            auto [i, fa] = stk.back();
            stk.pop_back();
            for (int j : g[i]) {
                if (j != fa) {
                    cnt += get(i, j);
                    stk.push_back({j, i});
                }
            }
        }
        int ans = 0;
        vector<array<int, 3>> walk{{0, -1, cnt}};
        while (!walk.empty()) {
            auto [i, fa, c] = walk.back();
            walk.pop_back();
            ans += c >= k;
            for (int j : g[i]) {
                if (j != fa) {
                    walk.push_back({j, i, c - get(i, j) + get(j, i)});
                }
            }
        }
        return ans;
    }
};
 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
38
39
40
41
42
43
44
45
func rootCount(edges [][]int, guesses [][]int, k int) (ans int) {
    n := len(edges) + 1
    g := make([][]int, n)
    for _, e := range edges {
        a, b := e[0], e[1]
        g[a] = append(g[a], b)
        g[b] = append(g[b], a)
    }
    gs := map[int]int{}
    f := func(i, j int) int {
        return i*n + j
    }
    for _, e := range guesses {
        a, b := e[0], e[1]
        gs[f(a, b)]++
    }
    cnt := 0
    type frame struct{ i, fa int }
    stk := []frame{{0, -1}}
    for len(stk) > 0 {
        cur := stk[len(stk)-1]
        stk = stk[:len(stk)-1]
        for _, j := range g[cur.i] {
            if j != cur.fa {
                cnt += gs[f(cur.i, j)]
                stk = append(stk, frame{j, cur.i})
            }
        }
    }
    type step struct{ i, fa, c int }
    walk := []step{{0, -1, cnt}}
    for len(walk) > 0 {
        cur := walk[len(walk)-1]
        walk = walk[:len(walk)-1]
        if cur.c >= k {
            ans++
        }
        for _, j := range g[cur.i] {
            if j != cur.fa {
                walk = append(walk, step{j, cur.i, cur.c - gs[f(cur.i, j)] + gs[f(j, cur.i)]})
            }
        }
    }
    return
}
 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
38
39
40
41
function rootCount(edges: number[][], guesses: number[][], k: number): number {
    const n = edges.length + 1;
    const g: number[][] = Array.from({ length: n }, () => []);
    const gs: Map<number, number> = new Map();
    const f = (i: number, j: number) => i * n + j;
    for (const [a, b] of edges) {
        g[a].push(b);
        g[b].push(a);
    }
    for (const [a, b] of guesses) {
        const x = f(a, b);
        gs.set(x, (gs.get(x) || 0) + 1);
    }
    let cnt = 0;
    const stk: number[][] = [[0, -1]];
    while (stk.length) {
        const [i, fa] = stk.pop()!;
        for (const j of g[i]) {
            if (j !== fa) {
                cnt += gs.get(f(i, j)) || 0;
                stk.push([j, i]);
            }
        }
    }
    let ans = 0;
    const walk: number[][] = [[0, -1, cnt]];
    while (walk.length) {
        const [i, fa, c] = walk.pop()!;
        if (c >= k) {
            ans++;
        }
        for (const j of g[i]) {
            if (j !== fa) {
                const a = gs.get(f(i, j)) || 0;
                const b = gs.get(f(j, i)) || 0;
                walk.push([j, i, c - a + b]);
            }
        }
    }
    return ans;
}

评论