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\) 這棵子樹沒有搜完的情況下,讓它子樹裡的節點的並查集找祖先時找到的是它。