Tarjan算法

Tarjan算法

Tarjan算法求LCA

离线求LCA O ( n + m ) O(n + m) O(n+m)

Tarjan算法本质是使用并查集对“向上标记法”的优化。
在深度优先遍历的任意时刻,树中节点分为3类:

  1. 已经访问完毕并且回溯的节点。在这些节点标记一个整数2.
  2. 已经开始递归,但尚未回溯的节点。这些节点就是当前正在访问的节点x以及x的祖先。在这些节点上标记一个整数1.
  3. 尚未访问的节点。这些节点没有标记。

对于正在访问的节点 x x x,它到根节点的路径已经标记为1.若y是已经访问完毕并且回溯的节点,则LCA(x, y)就是从y向上走到根,第一个遇到标记为1的节点。
可以利用并查集进行优化,当一个节点获得整数2的标记时,把它所在的集合合并到它父节点所在的集合中(合并时它的父节点的标记一定为1,且单独构成一个集合)。
这相当于每个完成回溯的节点都有一个指针指向它的父节点,只需要查询y所在集合代表元素,就等价于y一直向上一直走到一个开始递归但尚未回溯的节点(标记1),即LCA(x, y)

距离

给出 n 个点的一棵树,多次询问两点之间的最短距离。

注意:

边是无向的。
所有节点的编号是 1,2,…,n。
输入格式
第一行为两个整数 n 和 m。n 表示点数,m 表示询问次数;

下来 n−1 行,每行三个整数 x,y,k,表示点 x 和点 y 之间存在一条边长度为 k;

再接下来 m 行,每行两个整数 x,y,表示询问点 x 到点 y 的最短距离。

树中结点编号从 1 到 n。

输出格式
共 m 行,对于每次询问,输出一行询问结果。

数据范围
2≤n≤104,
1≤m≤2×104,
0<k≤100,
1≤x,y≤n
输入样例1:
2 2
1 2 100
1 2
2 1
输出样例1:
100
100
输入样例2:
3 2
1 2 10
3 1 15
1 2
3 2
输出样例2:
10
25

N = 10010
M = N * 2

h = [-1] * N
e = [0] * M
w = [0] * M
ne = [-1] * M
idx = 0
dist = [0] * N
res = [0] * M
st = [0] * N
p = [0] * N
query = [[] for _ in range(N)]

def add(a, b, c) :
    global idx
    e[idx] = b
    w[idx] = c
    ne[idx] = h[a]
    h[a] = idx
    idx += 1
def find(x) :
	if x != p[x] : p[x] = find(p[x])
	return p[x]

def dfs(u, fa) : #求每个节点到根节点距离
	i = h[u]
	while ~ i :
		j = e[i]
		if j != fa : #防止回父节点
			dist[j] = dist[u] + w[i]
			dfs(j, u)
		i = ne[i]
def tarjan(u) :
	st[u] = 1
	i = h[u]
	while ~ i :
		j = e[i]
		if not st[j] : # 防止多次遍历
			tarjan(j)
			p[j] = u # 回溯完合并到此时为1的节点的集合
		i = ne[i]
	for item in query[u] :
		y, id = item[0], item[1]
		anc = find(y)
		if st[y] == 2 :
			res[id] = dist[u] + dist[y] - dist[anc] * 2
	st[u] = 2 #标记回溯

n, m = map(int, input().split())

for i in range(n - 1) :
    a, b, c = map(int, input().split())
    add(a, b, c), add(b, a, c)
    
for i in range(1, n + 1) : #初始化并查集
    p[i] = i

for i in range(m) :
    a, b = map(int, input().split())
    if a != b :
        query[a].append([b, i])
        query[b].append([a, i])

dfs(1, -1)    
tarjan(1)

for i in range(m) :
    print(res[i])

有向图的强连通分量

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值