Tarjan算法
Tarjan算法
文章目录
- Tarjan算法
- Tarjan算法求LCA
- 距离
- 有向图的强连通分量
Tarjan算法求LCA
离线求LCA O ( n + m ) O(n + m) O(n+m)
Tarjan算法本质是使用并查集对“向上标记法”的优化。
在深度优先遍历的任意时刻,树中节点分为3类:
- 已经访问完毕并且回溯的节点。在这些节点标记一个整数2.
- 已经开始递归,但尚未回溯的节点。这些节点就是当前正在访问的节点x以及x的祖先。在这些节点上标记一个整数1.
- 尚未访问的节点。这些节点没有标记。
对于正在访问的节点
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])