2025-11-06:包含给定路径的最小带权子树Ⅱ。用go语言,给一个带权无向树,节点编号从 0 到 n-1。用一个长度为 n-
2025-11-06:包含给定路径的最小带权子树Ⅱ。用go语言,给一个带权无向树,节点编号从 0 到 n-1。用一个长度为 n-1 的数组 edges 描述边,每个元素是三元组 [u, v, w],表示节点 u 和 v 之间有一条权重为 w 的无向边。
再给一组查询 queries,每个查询是三元组 [s1, s2, t]。对于每个查询,需要找出一棵“子树”——即从原树中选取的一组连通的节点与边(仍然是无环的连通子图)——满足在这棵子树中,s1 和 s2 都能经过子树中的边到达 t。要求返回使得所选子树中边权总和最小的那个值。
输出是一个数组,数组中第 j 个元素对应 queries[j] 的最小边权和。
3 <= n <= 100000。
edges.length == n - 1。
edges[i].length == 3。
0 <= ui, vi < n。
1 <= wi <= 10000。
1 <= queries.length <= 100000。
queries[j].length == 3。
0 <= src1j, src2j, destj < n。
src1j、src2j 和 destj 互不不同。
输入数据保证 edges 表示的是一棵有效的树。
输入: edges = [[0,1,2],[1,2,3],[1,3,5],[1,4,4],[2,5,6]], queries = [[2,3,4],[0,2,5]]。
输出: [12,11]。
解释:
蓝色边表示可以得到最优答案的子树之一。

answer[0]:在选出的子树中,从 src1 = 2 和 src2 = 3 到 dest = 4 的路径总权重为 3 + 5 + 4 = 12。
answer[1]:在选出的子树中,从 src1 = 0 和 src2 = 2 到 dest = 5 的路径总权重为 2 + 3 + 6 = 11。
题目来自力扣3553。
📝 分步详解
第一步:构建图的邻接表
首先,程序需要将输入的边数组 edges 转换成一种便于后续遍历和计算的数据结构。这里使用的是邻接表来表示这棵树。
- 过程:遍历输入的每一条边。对于边
[u, v, w],在邻接表中,为节点u的记录里增加一个邻居v及边权w,同时为节点v的记录里增加邻居u及边权w。这样就构建了一个无向带权图的完整连接关系。 - 目的:邻接表可以高效地访问任意节点的所有邻居及对应的边权,为后续的深度优先搜索(DFS)做准备 。
第二步:预处理LCA所需信息
为了快速求解任意两点的最近公共祖先,代码进行了预处理,计算并存储每个节点的深度、到根节点的距离(边权和)以及一个用于倍增法的祖先数组 pa。
-
深度优先搜索(DFS)初始化:
- 选择节点
0作为树的根节点。 - 通过一次DFS遍历,计算每个节点相对于根节点的深度(
dep数组)和累计边权(dis数组,即从根节点到该节点的路径上的总权重)。 - 同时,记录每个节点的直接父节点(即
pa[x][0])。
- 选择节点
-
倍增数组填充:
- 数组
pa[x][i]表示节点x向上跳 步后到达的祖先节点。 - 这个数组利用动态规划的思想进行填充:
pa[x][i] = pa[ pa[x][i-1] ][i-1]。这一步是倍增算法的核心,它允许我们以对数时间快速向上跳跃 。
- 数组
第三步:实现LCA查询函数
基于预处理好的信息,程序实现了两个关键函数来支持查询:
uptoDep函数:将一个节点x向上提升到指定的深度d。getLCA函数:求解两个节点x和y的最近公共祖先。- 首先,将深度较大的节点提升到与另一个节点相同的深度。
- 然后,如果此时两节点相同,则该节点即为LCA。
- 如果不同,则让两个节点同时向上倍增跳跃,直到它们再跳一步父节点就相同为止,此时的父节点就是LCA 。
getDis函数:利用公式dis[x] + dis[y] - 2*dis[lca]计算树上两点x和y之间的路径边权和。这是因为从x到y的路径必然经过它们的LCA,该公式巧妙地减去了从根节点到LCA的重复计算部分 。
第四步:处理查询并计算答案
对于每一个查询 [a, b, c](对应题目中的 [s1, s2, t]),程序并不需要真正地构造出子树。它利用了一个关键的数学洞察:
- 核心公式:所求的最小边权总和等于
(getDis(a, c) + getDis(b, c) - getDis(a, b)) / 2。不过,根据你提供的代码,实际计算使用的是(getDis(a, b) + getDis(b, c) + getDis(a, c)) / 2。这两个公式在数学上是等价的,可以相互推导。 - 逻辑解释:满足条件的子树,本质上必须包含连接
a,b,c三点的最小 Steiner 树。在这棵树上,从a到c和从b到c的路径会有重叠部分。这个公式的目的就是只计算一次所有边的权重,避免重复计算重叠路径的权重。最终结果就是所需子树的最小边权和 。
💡 复杂度分析
-
时间复杂度:
- 预处理阶段(DFS和倍增数组填充):DFS遍历所有节点,时间复杂度为 。填充倍增数组需要遍历每个节点和每个二进制位( 级别),因此是 。
- 查询处理阶段:每次查询都进行固定次数(通常为3次)的LCA查询。每次LCA查询的时间复杂度为 。因此,处理所有 个查询的总时间是 。
- 总时间复杂度:。这在给定的数据规模()下是高效的。
-
额外空间复杂度:
- 主要空间开销在于存储邻接表()、深度数组()、距离数组()和倍增祖先数组()。
- 因此,总的额外空间复杂度为 。
Go完整代码如下:
package main
import (
"fmt"
"math/bits"
)
func minimumWeight(edges [][]int, queries [][]int) []int {
n := len(edges) + 1
type edge struct{ to, wt int }
g := make([][]edge, n)
for _, e := range edges {
x, y, wt := e[0], e[1], e[2]
g[x] = append(g[x], edge{y, wt})
g[y] = append(g[y], edge{x, wt})
}
const mx = 17
pa := make([][mx]int, n)
dep := make([]int, n)
dis := make([]int, n)
var dfs func(int, int)
dfs = func(x, p int) {
pa[x][0] = p
for _, e := range g[x] {
y := e.to
if y == p {
continue
}
dep[y] = dep[x] + 1
dis[y] = dis[x] + e.wt
dfs(y, x)
}
}
dfs(0, -1)
for i := range mx - 1 {
for x := range pa {
p := pa[x][i]
if p != -1 {
pa[x][i+1] = pa[p][i]
} else {
pa[x][i+1] = -1
}
}
}
uptoDep := func(x, d int) int {
for k := uint(dep[x] - d); k > 0; k &= k - 1 {
x = pa[x][bits.TrailingZeros(k)]
}
return x
}
getLCA := func(x, y int) int {
if dep[x] > dep[y] {
x, y = y, x
}
y = uptoDep(y, dep[x])
if y == x {
return x
}
for i := mx - 1; i >= 0; i-- {
if pv, pw := pa[x][i], pa[y][i]; pv != pw {
x, y = pv, pw
}
}
return pa[x][0]
}
getDis := func(x, y int) int { return dis[x] + dis[y] - dis[getLCA(x, y)]*2 }
// 以上全是 LCA 模板
ans := make([]int, len(queries))
for i, q := range queries {
a, b, c := q[0], q[1], q[2]
ans[i] = (getDis(a, b) + getDis(b, c) + getDis(a, c)) / 2
}
return ans
}
func main() {
edges := [][]int{{0, 1, 2}, {1, 2, 3}, {1, 3, 5}, {1, 4, 4}, {2, 5, 6}}
queries := [][]int{{2, 3, 4}, {0, 2, 5}}
result := minimumWeight(edges, queries)
fmt.Println(result)
}

Python完整代码如下:
# -*-coding:utf-8-*-
import sys
sys.setrecursionlimit(100000)
def minimumWeight(edges, queries):
n = len(edges) + 1
g = [[] for _ in range(n)]
for e in edges:
x, y, wt = e[0], e[1], e[2]
g[x].append((y, wt))
g[y].append((x, wt))
mx = 17
pa = [[-1] * mx for _ in range(n)]
dep = [0] * n
dis = [0] * n
def dfs(x, p):
pa[x][0] = p
for y, wt in g[x]:
if y == p:
continue
dep[y] = dep[x] + 1
dis[y] = dis[x] + wt
dfs(y, x)
dfs(0, -1)
for i in range(mx - 1):
for x in range(n):
p = pa[x][i]
if p != -1:
pa[x][i + 1] = pa[p][i]
else:
pa[x][i + 1] = -1
def uptoDep(x, d):
k = dep[x] - d
while k > 0:
x = pa[x][(k & -k).bit_length() - 1]
k &= k - 1
return x
def getLCA(x, y):
if dep[x] > dep[y]:
x, y = y, x
y = uptoDep(y, dep[x])
if y == x:
return x
for i in range(mx - 1, -1, -1):
if pa[x][i] != pa[y][i]:
x, y = pa[x][i], pa[y][i]
return pa[x][0]
def getDis(x, y):
return dis[x] + dis[y] - dis[getLCA(x, y)] * 2
ans = []
for q in queries:
a, b, c = q[0], q[1], q[2]
ans.append((getDis(a, b) + getDis(b, c) + getDis(a, c)) // 2)
return ans
if __name__ == "__main__":
edges = [[0, 1, 2], [1, 2, 3], [1, 3, 5], [1, 4, 4], [2, 5, 6]]
queries = [[2, 3, 4], [0, 2, 5]]
result = minimumWeight(edges, queries)
print(result)

C++完整代码如下:
#include <iostream>
#include <vector>
#include <array>
#include <algorithm>
using namespace std;
struct Edge {
int to, wt;
};
vector<vector<Edge>> g;
vector<array<int, 18>> pa;
vector<int> dep, dis;
int n;
void dfs(int x, int p) {
pa[x][0] = p;
for (auto &e : g[x]) {
int y = e.to;
if (y == p) continue;
dep[y] = dep[x] + 1;
dis[y] = dis[x] + e.wt;
dfs(y, x);
}
}
int uptoDep(int x, int d) {
int diff = dep[x] - d;
for (int k = 0; diff > 0; k++, diff >>= 1) {
if (diff & 1) x = pa[x][k];
}
return x;
}
int getLCA(int x, int y) {
if (dep[x] > dep[y]) swap(x, y);
y = uptoDep(y, dep[x]);
if (x == y) return x;
for (int i = 17; i >= 0; i--) {
if (pa[x][i] != pa[y][i]) {
x = pa[x][i];
y = pa[y][i];
}
}
return pa[x][0];
}
int getDis(int x, int y) {
return dis[x] + dis[y] - dis[getLCA(x, y)] * 2;
}
vector<int> minimumWeight(vector<vector<int>> &edges, vector<vector<int>> &queries) {
n = edges.size() + 1;
g.assign(n, {});
for (auto &e : edges) {
int x = e[0], y = e[1], wt = e[2];
g[x].push_back({y, wt});
g[y].push_back({x, wt});
}
pa.assign(n, {});
dep.assign(n, 0);
dis.assign(n, 0);
dfs(0, -1);
for (int i = 0; i < n; i++) {
for (int j = 1; j <= 17; j++) {
int p = pa[i][j-1];
pa[i][j] = (p == -1 ? -1 : pa[p][j-1]);
}
}
vector<int> ans;
for (auto &q : queries) {
int a = q[0], b = q[1], c = q[2];
ans.push_back((getDis(a, b) + getDis(b, c) + getDis(a, c)) / 2);
}
return ans;
}
int main() {
vector<vector<int>> edges = {{0, 1, 2}, {1, 2, 3}, {1, 3, 5}, {1, 4, 4}, {2, 5, 6}};
vector<vector<int>> queries = {{2, 3, 4}, {0, 2, 5}};
vector<int> result = minimumWeight(edges, queries);
for (auto r : result) cout << r << " ";
cout << endl;
return 0;
}

- 点赞
- 收藏
- 关注作者
评论(0)