2025-10-04:带权树中的最短路径。用go语言,给出一个节点编号为 1..n 的无向加权树,并以节点 1 作为根。用长度为

举报
福大大架构师每日一题 发表于 2025/10/04 06:59:31 2025/10/04
【摘要】 2025-10-04:带权树中的最短路径。用go语言,给出一个节点编号为 1…n 的无向加权树,并以节点 1 作为根。用长度为 n−1 的数组 edges 描述每条边,edges[i] = [ui, vi, wi] 表示节点 ui 与 vi 之间存在一条权值为 wi 的无向边。还给出一个长度为 q 的操作序列 queries,其中每个操作是两种类型之一:[1, u, v, w’]:把连接 u...

2025-10-04:带权树中的最短路径。用go语言,给出一个节点编号为 1…n 的无向加权树,并以节点 1 作为根。用长度为 n−1 的数组 edges 描述每条边,edges[i] = [ui, vi, wi] 表示节点 ui 与 vi 之间存在一条权值为 wi 的无向边。

还给出一个长度为 q 的操作序列 queries,其中每个操作是两种类型之一:

  • [1, u, v, w’]:把连接 u 与 v 的那条边的权重改为 w’(保证这条边在 edges 中存在);

  • [2, x]:询问从根节点 1 到节点 x 的最短路径长度(按当前边权计算)。

要求返回一个整数数组 answer,按操作顺序记录每个类型为 [2, x] 的查询结果。注意边权的修改会影响后续的查询结果。

1 <= n <= 100000。

edges.length == n - 1。

edges[i] == [ui, vi, wi]。

1 <= ui, vi <= n。

1 <= wi <= 10000。

输入保证 edges 构成一棵合法的树。

1 <= queries.length == q <= 100000。

queries[i].length == 2 或 4。

queries[i] == [1, u, v, w’],或者queries[i] == [2, x]。

1 <= u, v, x <= n。

(u, v) 一定是 edges 中的一条边。

1 <= w’ <= 10000。

输入: n = 3, edges = [[1,2,2],[1,3,4]], queries = [[2,1],[2,3],[1,1,3,7],[2,2],[2,3]]。

输出: [0,4,2,7]。

解释:

在这里插入图片描述

查询 [2,1]:从根节点 1 到节点 1 的最短路径为 0。

查询 [2,3]:从根节点 1 到节点 3 的最短路径为 4。

操作 [1,1,3,7]:边 (1,3) 的权重从 4 改为 7。

查询 [2,2]:从根节点 1 到节点 2 的最短路径为 2。

查询 [2,3]:从根节点 1 到节点 3 的最短路径为 7。

题目来自力扣3515。

解决方案步骤

1. 构建树结构

  • 使用邻接表 g 存储树结构
  • 每个节点存储其相邻节点列表

2. DFS遍历进行欧拉序编号

  • 从根节点1开始进行深度优先搜索(DFS)
  • 为每个节点分配进入时间 in[x] 和离开时间 out[x]
  • 这种编号方式使得以节点x为根的子树在区间 [in[x], out[x]]
  • 时间戳从1开始递增

3. 建立边权与节点的映射

  • 对于每条边 (u, v),将边权存储在子节点的 weight 数组中
  • 具体来说:如果v是u的子节点,则边权存储在 weight[v]
  • 通过DFS时的父子关系确定谁是子节点

4. 使用树状数组维护路径和

  • 创建大小为n+1的树状数组 diff
  • 关键思想:从根节点1到节点x的路径和 = diff.pre(in[x])
  • 当修改边权时,只更新以该边子节点为根的子树中的所有节点

5. 处理操作

对于修改操作 [1, u, v, w']

  • 确定哪个节点是子节点(通过比较 in 时间戳)
  • 计算边权的增量:d = 新权值 - 旧权值
  • 更新 weight 数组
  • 在树状数组中进行区间更新:将子树中所有节点的值增加 d
    • diff.update(in[y], d) 在子树的开始位置增加
    • diff.update(out[y]+1, -d) 在子树结束的下一个位置减少
    • 这样只有子树内的节点在查询前缀和时会包含这个增量

对于查询操作 [2, x]

  • 直接返回 diff.pre(in[x])
  • 因为树状数组的前缀和正好对应从根到节点x的路径和

示例执行过程

以题目中的例子说明:

  • 初始边权:(1,2)=2, (1,3)=4
  • DFS后:节点1的子树包含所有节点
  • 初始时,从1到3的路径和为4
  • 修改边 (1,3) 权值为7后,子树3中的所有节点(只有节点3)路径和增加3
  • 查询节点3时得到7

复杂度分析

时间复杂度

  • DFS遍历:O(n),每个节点访问一次
  • 每次修改操作:O(log n),树状数组的更新操作
  • 每次查询操作:O(log n),树状数组的查询操作
  • 总体:O(n + q log n),其中n是节点数,q是操作数

空间复杂度

  • 邻接表:O(n)
  • in/out数组:O(n)
  • weight数组:O(n)
  • 树状数组:O(n)
  • 总体:O(n)

这种方法利用了树的DFS序和树状数组的高效区间更新/单点查询特性,能够高效处理大规模的边权修改和路径查询。

Go完整代码如下:

package main

import (
	"fmt"
)

type fenwick []int

func newFenwickTree(n int) fenwick {
	return make(fenwick, n+1) // 使用下标 1 到 n
}

// a[i] 增加 val
// 1 <= i <= n
func (f fenwick) update(i, val int) {
	for ; i < len(f); i += i & -i {
		f[i] += val
	}
}

// 求前缀和 a[1] + ... + a[i]
// 1 <= i <= n
func (f fenwick) pre(i int) (s int) {
	for ; i > 0; i &= i - 1 {
		s += f[i]
	}
	return
}

func treeQueries(n int, edges [][]int, queries [][]int) (ans []int) {
	g := make([][]int, n+1)
	for _, e := range edges {
		x, y := e[0], e[1]
		g[x] = append(g[x], y)
		g[y] = append(g[y], x)
	}

	in := make([]int, n+1)
	out := make([]int, n+1)
	clock := 0
	var dfs func(int, int)
	dfs = func(x, fa int) {
		clock++
		in[x] = clock // 进来的时间
		for _, y := range g[x] {
			if y != fa {
				dfs(y, x)
			}
		}
		out[x] = clock // 离开的时间
	}
	dfs(1, 0)

	// 对于一条边 x-y(y 是 x 的儿子),把边权保存在 weight[y] 中
	weight := make([]int, n+1)
	diff := newFenwickTree(n)
	update := func(x, y, w int) {
		// 保证 y 是 x 的儿子
		if in[x] > in[y] {
			y = x
		}
		d := w - weight[y] // 边权的增量
		weight[y] = w
		// 把子树 y 中的最短路长度都增加 d(用差分树状数组维护)
		diff.update(in[y], d)
		diff.update(out[y]+1, -d)
	}

	for _, e := range edges {
		update(e[0], e[1], e[2])
	}

	for _, q := range queries {
		if q[0] == 1 {
			update(q[1], q[2], q[3])
		} else {
			ans = append(ans, diff.pre(in[q[1]]))
		}
	}
	return
}

func main() {
	n := 3
	edges := [][]int{{1, 2, 2}, {1, 3, 4}}
	queries := [][]int{{2, 1}, {2, 3}, {1, 1, 3, 7}, {2, 2}, {2, 3}}
	result := treeQueries(n, edges, queries)
	fmt.Println(result)
}

在这里插入图片描述

Python完整代码如下:

# -*-coding:utf-8-*-

class Fenwick:
    def __init__(self, n):
        self.n = n
        self.tree = [0] * (n + 1)  # Using indices 1 to n
    
    def update(self, i, val):
        while i < len(self.tree):
            self.tree[i] += val
            i += i & -i
    
    def pre(self, i):
        s = 0
        while i > 0:
            s += self.tree[i]
            i &= i - 1
        return s

def tree_queries(n, edges, queries):
    g = [[] for _ in range(n + 1)]
    for e in edges:
        x, y = e[0], e[1]
        g[x].append(y)
        g[y].append(x)
    
    in_time = [0] * (n + 1)
    out_time = [0] * (n + 1)
    clock = 0
    
    def dfs(x, fa):
        nonlocal clock
        clock += 1
        in_time[x] = clock  # Entry time
        for y in g[x]:
            if y != fa:
                dfs(y, x)
        out_time[x] = clock  # Exit time
    
    dfs(1, 0)
    
    # For an edge x-y (where y is x's child), store the edge weight in weight[y]
    weight = [0] * (n + 1)
    diff = Fenwick(n)
    
    def update_edge(x, y, w):
        # Ensure y is x's child
        if in_time[x] > in_time[y]:
            y = x
        d = w - weight[y]  # Increment in edge weight
        weight[y] = w
        # Increase the shortest path length in subtree y by d (using Fenwick tree)
        diff.update(in_time[y], d)
        diff.update(out_time[y] + 1, -d)
    
    for e in edges:
        update_edge(e[0], e[1], e[2])
    
    ans = []
    for q in queries:
        if q[0] == 1:
            update_edge(q[1], q[2], q[3])
        else:
            ans.append(diff.pre(in_time[q[1]]))
    return ans

def main():
    n = 3
    edges = [[1, 2, 2], [1, 3, 4]]
    queries = [[2, 1], [2, 3], [1, 1, 3, 7], [2, 2], [2, 3]]
    result = tree_queries(n, edges, queries)
    print(result)

if __name__ == "__main__":
    main()

在这里插入图片描述

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。