Tarjan演算法求LCA(最近公共祖先)
阿新 • • 發佈:2019-01-23
LCA的離線演算法。複雜度為O(n+q)。
這個演算法充分利用了dfs樹的結構。
對於每個節點u,關於它的詢問(u,v)只有兩種。(假設先dfs(u)後dfs(v))
1、v在u的子樹內。
此時LCA(u,v) = u.
2、v不在u的子樹內。
⑴假設v在u的父親的另一棵子樹內。
此時LCA(u,v) = father[u].
⑵如果不滿足條件⑴,則v可能在u的父親的父親的另一棵子樹內。
而此時LCA(u,v) = father[ father[u] ].
⑶……
觀察一下,是不是發現了什麼呢?
沒錯,不論是哪種情況,LCA(u,v)都與u和father[ ]有某種關係。我們能不能抓住這種關係呢?
我們繼續觀察,一直向上取father[ ],貌似和並查集的FIND操作很像呢。
我們用並查集的角度依次考慮上面的情況試試看。
1、v在u的子樹內。
此時dfs(u)還在棧中,沒有執行完,此時沒有向上取father[ ],說明此時u是根。
2、v不在u的子樹內。
⑴假設v在u的父親的另一棵子樹內。
此時的dfs(u)已經執行完並出棧。此時向上取了一次father[ ],說明此時u的父親是根。
⑵如果不滿足條件⑴,則v可能在u的父親的父親的另一棵子樹內。
同理,此時dfs(u的父親)也已經執行完並出棧。此時向上取了兩次father[ ],說明此時u的父親的父親是根。
⑶……
綜上,我們只要保證當dfs(u)在棧中的時候,u是根;當dfs(u)不在棧中的時候,father[u]是根就行了。
#include <stdio.h> #include <string.h> #include <algorithm> using namespace std; #define FileIn freopen("in.ads","r",stdin) #define FileOut freopen("out.ads","w",stdout) #define N 155 #define M 22555 struct Vertex { int head; }V[N],Qv[N]; struct Edge { int v,next; }E[M],Qe[M]; int top,pre[N]; bool used[N]; void Init() { top = 0; memset(V,-1,sizeof(V)); memset(Qv,-1,sizeof(Qv)); memset(pre,-1,sizeof(pre)); memset(used,0,sizeof(used)); } int Root(int x) { if(pre[x] != -1) return pre[x] = Root(pre[x]); else return x; } void Union(int a,int b) { int r1 = Root(a); int r2 = Root(b); if(r1 != r2) pre[r2] = r1; } void Add_Edge(int u,int v) { E[top].v = v; E[top].next = V[u].head; V[u].head = top++; } void Add_Qedge(int u,int v) { Qe[top].v = v; Qe[top].next = Qv[u].head; Qv[u].head = top++; } void Tarjan(int u) { used[u] = true; for(int i=Qv[u].head;i!=-1;i=Qe[i].next) { int v = Qe[i].v; if(used[v]) printf("The LCA of (%d,%d) is -> %d\n",u,v,Root(v)); } for(int i=V[u].head;i!=-1;i=E[i].next) { int v= E[i].v; if(used[v]) continue; Tarjan(v); Union(u,v); } } int main() { FileIn; int n,m,u,v,Q; while(~scanf("%d%d",&n,&m)) { Init(); while(m--) { scanf("%d%d",&u,&v); Add_Edge(u,v); Add_Edge(v,u); } scanf("%d",&Q); while(Q--) { scanf("%d%d",&u,&v); Add_Qedge(u,v); Add_Qedge(v,u); } Tarjan(1); } return 0; }