「學習筆記」tarjan 求最近公共祖先

yi_fan0305發表於2023-04-30

Tarjan 演算法是一種 離線演算法,需要使用並查集記錄某個結點的祖先結點。
並沒有傳說中的那麼快。

過程

將詢問都記錄下來,將它們建成正向邊和反向邊。
在 dfs 的過程中,給走過的節點打上標記,同時維護並查集,這裡利用了回溯的思想,如果 \(u\) 節點的這棵子樹沒搜完,那麼 fa[u] = u;,搜完後,在更新並查集。
我們假設查詢 \(u\)\(v\) 的最近公共祖先,搜到節點 \(u\),如果另一個節點 \(v\) 已經被搜到過了,那麼 \(v\) 點的並查集祖先就是 \(u\)\(v\) 的最近公共祖先。

如果第一次查詢 \(v\) 點時,發現 \(v\) 點已經被搜到了,說明 \(u\)\(v\) 點在同一棵子樹中,並且這個子樹是所有包括了 \(u\) 點和 \(v\) 點的子樹中 siz 最小的,這棵子樹的根也是在所有符合條件的子樹的根中離 \(u\)\(v\) 最近的,即這棵子樹的根就是 \(u\)\(v\) 的最近公共祖先,而 \(v\) 的並查集祖先就是這個根節點。

程式碼

結構體記錄詢問

int cnt = 1;

struct query {
	int v, lca, nxt;
} q[N << 1];

記錄詢問、初始化

for (int i = 1, x, y; i <= m; ++ i) {
	scanf("%d%d", &x, &y);
	add_query(x, y);
	add_query(y, x);
}
for (int i = 1; i <= n; ++ i) {
	fa[i] = i;
}

tarjan、並查集

void tarjan(int u) {
	fa[u] = u;
	vis[u] = 1;
	for (int v : son[u]) {
		if (!vis[v]) {
			tarjan(v);
			fa[v] = u;
		}
	}
	int v;
	for (int i = h[u]; i; i = q[i].nxt) {
		if (vis[v = q[i].v]) {
			q[i].lca = q[i ^ 1].lca = find(v);
		}
	}
}

fa[u] = u; 是為了保證在 \(u\) 這棵子樹沒有搜完的情況下,讓它子樹裡的節點的並查集找祖先時找到的是它。

相關文章