|
| 1 | +# https://leetcode.com/problems/number-of-ways-to-assign-edge-weights-ii/description/ |
| 2 | + |
| 3 | + |
| 4 | +""" |
| 5 | +There is an undirected tree with n nodes labeled from 1 to n, rooted at node 1. The tree is represented by a 2D integer array edges of length n - 1, where edges[i] = [ui, vi] indicates that there is an edge between nodes ui and vi. |
| 6 | +Initially, all edges have a weight of 0. You must assign each edge a weight of either 1 or 2. |
| 7 | +The cost of a path between any two nodes u and v is the total weight of all edges in the path connecting them. |
| 8 | +You are given a 2D integer array queries. For each queries[i] = [ui, vi], determine the number of ways to assign weights to edges in the path such that the cost of the path between ui and vi is odd. |
| 9 | +Return an array answer, where answer[i] is the number of valid assignments for queries[i]. |
| 10 | +Since the answer may be large, apply modulo 109 + 7 to each answer[i]. |
| 11 | +Note: For each query, disregard all edges not in the path between node ui and vi. |
| 12 | +
|
| 13 | +Example 1: |
| 14 | +Input: edges = [[1,2]], queries = [[1,1],[1,2]] |
| 15 | +Output: [0,1] |
| 16 | +Explanation: |
| 17 | +Query [1,1]: The path from Node 1 to itself consists of no edges, so the cost is 0. Thus, the number of valid assignments is 0. |
| 18 | +Query [1,2]: The path from Node 1 to Node 2 consists of one edge (1 → 2). Assigning weight 1 makes the cost odd, while 2 makes it even. Thus, the number of valid assignments is 1. |
| 19 | +
|
| 20 | +Example 2: |
| 21 | +Input: edges = [[1,2],[1,3],[3,4],[3,5]], queries = [[1,4],[3,4],[2,5]] |
| 22 | +Output: [2,1,4] |
| 23 | +Explanation: |
| 24 | +Query [1,4]: The path from Node 1 to Node 4 consists of two edges (1 → 3 and 3 → 4). Assigning weights (1,2) or (2,1) results in an odd cost. Thus, the number of valid assignments is 2. |
| 25 | +Query [3,4]: The path from Node 3 to Node 4 consists of one edge (3 → 4). Assigning weight 1 makes the cost odd, while 2 makes it even. Thus, the number of valid assignments is 1. |
| 26 | +Query [2,5]: The path from Node 2 to Node 5 consists of three edges (2 → 1, 1 → 3, and 3 → 5). Assigning (1,2,2), (2,1,2), (2,2,1), or (1,1,1) makes the cost odd. Thus, the number of valid assignments is 4. |
| 27 | +
|
| 28 | +
|
| 29 | +Constraints: |
| 30 | +2 <= n <= 105 |
| 31 | +edges.length == n - 1 |
| 32 | +edges[i] == [ui, vi] |
| 33 | +1 <= queries.length <= 105 |
| 34 | +queries[i] == [ui, vi] |
| 35 | +1 <= ui, vi <= n |
| 36 | +edges represents a valid tree. |
| 37 | +""" |
| 38 | + |
| 39 | + |
| 40 | +class Solution: |
| 41 | + MOD = 10**9 + 7 |
| 42 | + |
| 43 | + def modPow(self, a, b): |
| 44 | + ans = 1 |
| 45 | + while b > 0: |
| 46 | + if b & 1: |
| 47 | + ans = (ans * a) % self.MOD |
| 48 | + a = (a * a) % self.MOD |
| 49 | + b >>= 1 |
| 50 | + return ans |
| 51 | + |
| 52 | + def dfs(self, node, parent): |
| 53 | + self.up[node][0] = parent |
| 54 | + |
| 55 | + for j in range(1, self.LOG): |
| 56 | + self.up[node][j] = self.up[self.up[node][j - 1]][j - 1] |
| 57 | + |
| 58 | + for neighbour in self.adj[node]: |
| 59 | + if neighbour == parent: |
| 60 | + continue |
| 61 | + |
| 62 | + self.depth[neighbour] = self.depth[node] + 1 |
| 63 | + self.dfs(neighbour, node) |
| 64 | + |
| 65 | + def lca(self, u, v): |
| 66 | + if self.depth[u] < self.depth[v]: |
| 67 | + u, v = v, u |
| 68 | + |
| 69 | + diff = self.depth[u] - self.depth[v] |
| 70 | + |
| 71 | + for j in range(self.LOG - 1, -1, -1): |
| 72 | + if diff & (1 << j): |
| 73 | + u = self.up[u][j] |
| 74 | + |
| 75 | + if u == v: |
| 76 | + return u |
| 77 | + |
| 78 | + for j in range(self.LOG - 1, -1, -1): |
| 79 | + if self.up[u][j] != self.up[v][j]: |
| 80 | + u = self.up[u][j] |
| 81 | + v = self.up[v][j] |
| 82 | + return self.up[u][0] |
| 83 | + |
| 84 | + def assignEdgeWeights( |
| 85 | + self, edges: List[List[int]], queries: List[List[int]] |
| 86 | + ) -> List[int]: |
| 87 | + n = len(edges) + 1 |
| 88 | + |
| 89 | + self.LOG = 1 |
| 90 | + while (1 << self.LOG) <= n: |
| 91 | + self.LOG += 1 |
| 92 | + |
| 93 | + self.adj = [[] for _ in range(n + 1)] |
| 94 | + |
| 95 | + for u, v in edges: |
| 96 | + self.adj[u].append(v) |
| 97 | + self.adj[v].append(u) |
| 98 | + |
| 99 | + self.depth = [0] * (n + 1) |
| 100 | + self.up = [[0] * self.LOG for _ in range(n + 1)] |
| 101 | + |
| 102 | + self.dfs(1, 0) |
| 103 | + ans = [] |
| 104 | + |
| 105 | + for u, v in queries: |
| 106 | + L = self.lca(u, v) |
| 107 | + dist = self.depth[u] + self.depth[v] - 2 * self.depth[L] |
| 108 | + |
| 109 | + if dist == 0: |
| 110 | + ans.append(0) |
| 111 | + else: |
| 112 | + ans.append(self.modPow(2, dist - 1)) |
| 113 | + return ans |
| 114 | + |
| 115 | +""" |
| 116 | +Complexity Analysis |
| 117 | +Let n be the number of nodes in the tree, and let m be the number of queries. |
| 118 | +
|
| 119 | +Time complexity: O(nlogn+mlogn). |
| 120 | +Building the binary lifting table requires O(nlogn) time. Each LCA query takes O(logn) time, resulting in a total query cost of O(mlogn). |
| 121 | +
|
| 122 | +Space complexity: O(nlogn). |
| 123 | +The binary lifting table stores O(logn) ancestors for each node, requiring O(nlogn) space. |
| 124 | +""" |
0 commit comments