100018. 边权重均等查询
现有一棵由 n
个节点组成的无向树,节点按从 0
到 n - 1
编号。给你一个整数 n
和一个长度为 n - 1
的二维整数数组 edges
,其中 edges[i] = [ui, vi, wi]
表示树中存在一条位于节点 ui
和节点 vi
之间、权重为 wi
的边。
另给你一个长度为 m
的二维整数数组 queries
,其中 queries[i] = [ai, bi]
。对于每条查询,请你找出使从 ai
到 bi
路径上每条边的权重相等所需的 最小操作次数 。在一次操作中,你可以选择树上的任意一条边,并将其权重更改为任意值。
注意:
ai
到 bi
的路径是一个由 不同 节点组成的序列,从节点 ai
开始,到节点 bi
结束,且序列中相邻的两个节点在树中共享一条边。返回一个长度为 m
的数组 answer
,其中 answer[i]
是第 i
条查询的答案。
示例 1:
输入:n = 7, edges = [[0,1,1],[1,2,1],[2,3,1],[3,4,2],[4,5,2],[5,6,2]], queries = [[0,3],[3,6],[2,6],[0,6]] 输出:[0,0,1,3] 解释:第 1 条查询,从节点 0 到节点 3 的路径中的所有边的权重都是 1 。因此,答案为 0 。 第 2 条查询,从节点 3 到节点 6 的路径中的所有边的权重都是 2 。因此,答案为 0 。 第 3 条查询,将边 [2,3] 的权重变更为 2 。在这次操作之后,从节点 2 到节点 6 的路径中的所有边的权重都是 2 。因此,答案为 1 。 第 4 条查询,将边 [0,1]、[1,2]、[2,3] 的权重变更为 2 。在这次操作之后,从节点 0 到节点 6 的路径中的所有边的权重都是 2 。因此,答案为 3 。 对于每条查询 queries[i] ,可以证明 answer[i] 是使从 ai 到 bi 的路径中的所有边的权重相等的最小操作次数。
示例 2:
输入:n = 8, edges = [[1,2,6],[1,3,4],[2,4,6],[2,5,3],[3,6,6],[3,0,8],[7,0,2]], queries = [[4,6],[0,4],[6,5],[7,4]] 输出:[1,2,2,3] 解释:第 1 条查询,将边 [1,3] 的权重变更为 6 。在这次操作之后,从节点 4 到节点 6 的路径中的所有边的权重都是 6 。因此,答案为 1 。 第 2 条查询,将边 [0,3]、[3,1] 的权重变更为 6 。在这次操作之后,从节点 0 到节点 4 的路径中的所有边的权重都是 6 。因此,答案为 2 。 第 3 条查询,将边 [1,3]、[5,2] 的权重变更为 6 。在这次操作之后,从节点 6 到节点 5 的路径中的所有边的权重都是 6 。因此,答案为 2 。 第 4 条查询,将边 [0,7]、[0,3]、[1,3] 的权重变更为 6 。在这次操作之后,从节点 7 到节点 4 的路径中的所有边的权重都是 6 。因此,答案为 3 。 对于每条查询 queries[i] ,可以证明 answer[i] 是使从 ai 到 bi 的路径中的所有边的权重相等的最小操作次数。
提示:
1 <= n <= 104
edges.length == n - 1
edges[i].length == 3
0 <= ui, vi < n
1 <= wi <= 26
edges
表示一棵有效的树1 <= queries.length == m <= 2 * 104
queries[i].length == 2
0 <= ai, bi < n
原站题解
javascript 解法, 执行用时: 652 ms, 内存消耗: 89.9 MB, 提交时间: 2024-01-26 09:34:34
var find = function(uf, i) { if (uf[i] === i) { return i; } uf[i] = find(uf, uf[i]); return uf[i]; } /** * @param {number} n * @param {number[][]} edges * @param {number[][]} queries * @return {number[]} */ var minOperationsQueries = function(n, edges, queries) { const m = queries.length; const W = 26; const neighbors = new Array(n).fill(null).map(() => new Map()); for (const edge of edges) { neighbors[edge[0]].set(edge[1], edge[2]); neighbors[edge[1]].set(edge[0], edge[2]); } const queryArr = new Array(n).fill(null).map(() => []); for (let i = 0; i < m; i++) { queryArr[queries[i][0]].push([queries[i][1], i]); queryArr[queries[i][1]].push([queries[i][0], i]); } const count = new Array(n).fill(null).map(() => new Array(W + 1).fill(0)); const visited = new Array(n).fill(0); const uf = new Array(n).fill(0); const lca = new Array(m).fill(0); const tarjan = (node, parent) => { if (parent !== -1) { count[node] = [...count[parent]]; count[node][neighbors[node].get(parent)] += 1; } uf[node] = node; for (const [child, weight] of neighbors[node]) { if (child == parent) { continue; } tarjan(child, node); uf[child] = node; } for (const [node1, index] of queryArr[node]) { if (node !== node1 && !visited[node1]) { continue; } lca[index] = find(uf, node1); } visited[node] = 1; }; tarjan(0, -1); const res = new Array(m).fill(0); for (let i = 0; i < m; i++) { let totalCount = 0; let maxCount = 0; for (let j = 1; j <= W; j++) { const t = count[queries[i][0]][j] + count[queries[i][1]][j] - 2 * count[lca[i]][j]; maxCount = Math.max(maxCount, t); totalCount += t; } res[i] = totalCount - maxCount; } return res; };
cpp 解法, 执行用时: 1776 ms, 内存消耗: 383 MB, 提交时间: 2023-09-04 10:08:07
class Solution { public: vector<int> minOperationsQueries(int n, vector<vector<int>> &edges, vector<vector<int>> &queries) { vector<vector<pair<int, int>>> g(n); for (auto &e: edges) { int x = e[0], y = e[1], w = e[2] - 1; g[x].emplace_back(y, w); g[y].emplace_back(x, w); } int m = 32 - __builtin_clz(n); // n 的二进制长度 vector<vector<int>> pa(n, vector<int>(m, -1)); vector<vector<array<int, 26>>> cnt(n, vector<array<int, 26>>(m)); vector<int> depth(n); function<void(int, int)> dfs = [&](int x, int fa) { pa[x][0] = fa; for (auto [y, w]: g[x]) { if (y != fa) { cnt[y][0][w] = 1; depth[y] = depth[x] + 1; dfs(y, x); } } }; dfs(0, -1); for (int i = 0; i < m - 1; i++) { for (int x = 0; x < n; x++) { int p = pa[x][i]; if (p != -1) { int pp = pa[p][i]; pa[x][i + 1] = pp; for (int j = 0; j < 26; ++j) { cnt[x][i + 1][j] = cnt[x][i][j] + cnt[p][i][j]; } } } } vector<int> ans; for (auto &q: queries) { int x = q[0], y = q[1]; int path_len = depth[x] + depth[y]; // 最后减去 depth[lca] * 2 int cw[26]{}; if (depth[x] > depth[y]) { swap(x, y); } // 让 y 和 x 在同一深度 for (int k = depth[y] - depth[x]; k; k &= k - 1) { int i = __builtin_ctz(k); int p = pa[y][i]; for (int j = 0; j < 26; ++j) { cw[j] += cnt[y][i][j]; } y = p; } if (y != x) { for (int i = m - 1; i >= 0; i--) { int px = pa[x][i], py = pa[y][i]; if (px != py) { for (int j = 0; j < 26; j++) { cw[j] += cnt[x][i][j] + cnt[y][i][j]; } x = px; y = py; // x 和 y 同时上跳 2^i 步 } } for (int j = 0; j < 26; j++) { cw[j] += cnt[x][0][j] + cnt[y][0][j]; } x = pa[x][0]; } int lca = x; path_len -= depth[lca] * 2; ans.push_back(path_len - *max_element(cw, cw + 26)); } return ans; } };
golang 解法, 执行用时: 472 ms, 内存消耗: 96 MB, 提交时间: 2023-09-04 10:07:30
func minOperationsQueries(n int, edges [][]int, queries [][]int) []int { type edge struct{ to, wt int } g := make([][]edge, n) for _, e := range edges { v, w, wt := e[0], e[1], e[2]-1 g[v] = append(g[v], edge{w, wt}) g[w] = append(g[w], edge{v, wt}) } const mx = 14 // 2^14 > 10^4 type pair struct { p int cnt [26]int } pa := make([][mx]pair, n) depth := make([]int, n) var build func(int, int, int) build = func(v, p, d int) { pa[v][0].p = p depth[v] = d for _, e := range g[v] { if w := e.to; w != p { pa[w][0].cnt[e.wt] = 1 build(w, v, d+1) } } } build(0, -1, 0) // 倍增模板 for i := 0; i+1 < mx; i++ { for v := range pa { if p := pa[v][i]; p.p != -1 { pp := pa[p.p][i] pa[v][i+1].p = pp.p for j := 0; j < 26; j++ { pa[v][i+1].cnt[j] = p.cnt[j] + pp.cnt[j] } } else { pa[v][i+1].p = -1 } } } // 计算 LCA 模板(这里返回最小操作次数) // https://leetcode.cn/problems/kth-ancestor-of-a-tree-node/solution/mo-ban-jiang-jie-shu-shang-bei-zeng-suan-v3rw/ f := func(v, w int) int { pathLen := depth[v] + depth[w] // 最后减去 depth[lca] * 2 cnt := [26]int{} if depth[v] > depth[w] { v, w = w, v } for i := 0; i < mx; i++ { if (depth[w]-depth[v])>>i&1 > 0 { p := pa[w][i] for j := 0; j < 26; j++ { cnt[j] += p.cnt[j] } w = p.p } } if w != v { for i := mx - 1; i >= 0; i-- { if pv, pw := pa[v][i], pa[w][i]; pv.p != pw.p { for j := 0; j < 26; j++ { cnt[j] += pv.cnt[j] + pw.cnt[j] } v, w = pv.p, pw.p } } for j := 0; j < 26; j++ { cnt[j] += pa[v][0].cnt[j] + pa[w][0].cnt[j] } v = pa[v][0].p } lca := v pathLen -= depth[lca] * 2 maxCnt := 0 for j := 0; j < 26; j++ { maxCnt = max(maxCnt, cnt[j]) } return pathLen - maxCnt } ans := make([]int, len(queries)) for i, q := range queries { ans[i] = f(q[0], q[1]) } return ans } func max(a, b int) int { if b > a { return b }; return a }
java 解法, 执行用时: 261 ms, 内存消耗: 89.5 MB, 提交时间: 2023-09-04 10:07:10
class Solution { public int[] minOperationsQueries(int n, int[][] edges, int[][] queries) { List<int[]>[] g = new ArrayList[n]; Arrays.setAll(g, e -> new ArrayList<>()); for (var e : edges) { int x = e[0], y = e[1], w = e[2] - 1; g[x].add(new int[]{y, w}); g[y].add(new int[]{x, w}); } int m = 32 - Integer.numberOfLeadingZeros(n); // n 的二进制长度 var pa = new int[n][m]; for (int i = 0; i < n; i++) { Arrays.fill(pa[i], -1); } var cnt = new int[n][m][26]; var depth = new int[n]; dfs(0, -1, g, pa, cnt, depth); for (int i = 0; i < m - 1; i++) { for (int x = 0; x < n; x++) { int p = pa[x][i]; if (p != -1) { int pp = pa[p][i]; pa[x][i + 1] = pp; for (int j = 0; j < 26; j++) { cnt[x][i + 1][j] = cnt[x][i][j] + cnt[p][i][j]; } } } } var ans = new int[queries.length]; for (int qi = 0; qi < queries.length; qi++) { int x = queries[qi][0], y = queries[qi][1]; int pathLen = depth[x] + depth[y]; var cw = new int[26]; if (depth[x] > depth[y]) { int temp = x; x = y; y = temp; } // 让 y 和 x 在同一深度 for (int k = depth[y] - depth[x]; k > 0; k &= k - 1) { int i = Integer.numberOfTrailingZeros(k); int p = pa[y][i]; for (int j = 0; j < 26; ++j) { cw[j] += cnt[y][i][j]; } y = p; } if (y != x) { for (int i = m - 1; i >= 0; i--) { int px = pa[x][i]; int py = pa[y][i]; if (px != py) { for (int j = 0; j < 26; j++) { cw[j] += cnt[x][i][j] + cnt[y][i][j]; } x = px; y = py; // x 和 y 同时上跳 2^i 步 } } for (int j = 0; j < 26; j++) { cw[j] += cnt[x][0][j] + cnt[y][0][j]; } x = pa[x][0]; } int lca = x; pathLen -= depth[lca] * 2; int maxCw = 0; for (int i = 0; i < 26; i++) { maxCw = Math.max(maxCw, cw[i]); } ans[qi] = pathLen - maxCw; } return ans; } private void dfs(int x, int fa, List<int[]>[] g, int[][] pa, int[][][] cnt, int[] depth) { pa[x][0] = fa; for (var e : g[x]) { int y = e[0], w = e[1]; if (y != fa) { cnt[y][0][w] = 1; depth[y] = depth[x] + 1; dfs(y, x, g, pa, cnt, depth); } } } }
python3 解法, 执行用时: 8128 ms, 内存消耗: 78.6 MB, 提交时间: 2023-09-04 10:06:53
class Solution: def minOperationsQueries(self, n: int, edges: List[List[int]], queries: List[List[int]]) -> List[int]: g = [[] for _ in range(n)] for x, y, w in edges: g[x].append((y, w - 1)) g[y].append((x, w - 1)) m = n.bit_length() pa = [[-1] * m for _ in range(n)] cnt = [[[0] * 26 for _ in range(m)] for _ in range(n)] depth = [0] * n def dfs(x: int, fa: int) -> None: pa[x][0] = fa for y, w in g[x]: if y != fa: cnt[y][0][w] = 1 depth[y] = depth[x] + 1 dfs(y, x) dfs(0, -1) # 倍增模板 for i in range(m - 1): for x in range(n): p = pa[x][i] if p != -1: pp = pa[p][i] pa[x][i + 1] = pp for j, (c1, c2) in enumerate(zip(cnt[x][i], cnt[p][i])): cnt[x][i + 1][j] = c1 + c2 ans = [] for x, y in queries: path_len = depth[x] + depth[y] # 最后减去 depth[lca] * 2 cw = [0] * 26 if depth[x] > depth[y]: x, y = y, x # 使 y 和 x 在同一深度 k = depth[y] - depth[x] for i in range(k.bit_length()): if (k >> i) & 1: # k 二进制从低到高第 i 位是 1 p = pa[y][i] for j, c in enumerate(cnt[y][i]): cw[j] += c y = p if y != x: for i in range(m - 1, -1, -1): px, py = pa[x][i], pa[y][i] if px != py: for j, (c1, c2) in enumerate(zip(cnt[x][i], cnt[y][i])): cw[j] += c1 + c2 x, y = px, py # 同时上跳 2^i 步 for j, (c1, c2) in enumerate(zip(cnt[x][0], cnt[y][0])): cw[j] += c1 + c2 x = pa[x][0] lca = x path_len -= depth[lca] * 2 ans.append(path_len - max(cw)) return ans