tarjan算法求LCA
LCA(Least Common Ancestors)的意思是最近公共祖先,即在一棵樹中,找出兩節點最近的公共祖先。
這里我們使用tarjan算法離線算法解決這個問題。
離線算法,是指首先讀入所有的詢問(求一次LCA叫做一次詢問),然后重新組織查詢處理順序以便得到更高效的處理方法。Tarjan算法是一個常見的用於解決LCA問題的離線算法,它結合了深度優先遍歷和並查集,整個算法為線性處理時間。
總思路就是每進入一個節點u的深搜,就把整個樹的一部分看作以節點u為根節點的小樹,再搜索其他的節點。每搜索完一個點后,如果該點和另一個已搜索完點為需要查詢LCA的點,則這兩點的LCA為另一個點的現在的祖先。
1.先建立兩個鏈表,一個為樹的各條邊,另一個是需要查詢最近公共祖先的兩節點。
2.建好后,從根節點開始進行一遍深搜。
3.先把該節點u的father設為他自己(也就是只看大樹的一部分,把那一部分看作是一棵樹),搜索與此節點相連的所有點v,如果點v沒被搜索過,則進入點v的深搜,深搜完后把點v的father設為點u。
4.深搜完一點u后,開始判斷節點u與另一節點v是否滿足求LCA的條件,滿足則將結果存入數組中。
5.搜索完所有點,自動退出初始的第一個深搜,輸出結果。

如上圖,根據實現算法可以看出,只有當某一棵子樹全部遍歷處理完成后,才將該子樹的根節點標記為有顏色(初始化是白色),假設程序按上面的樹形結構進行遍歷,首先從節點1開始,然后遞歸處理根為2的子樹,當子樹2處理完畢后,節點2, 5, 6均為紅色;接着要回溯處理3子樹,首先被染色的是節點7(因為節點7作為葉子不用深搜,直接處理),接着節點7就會查看所有詢問(7, x)的節點對,假如存在(7, 5),因為節點5已經被染黑,所以就可以斷定(7, 5)的最近公共祖先就是find(5),即節點1(因為2子樹處理完畢后,子樹2和節點1返回了合並后的樹的根1,此時樹根的祖先的值就是1)。
代碼實現:
1 #include<cstdio> 2 #define N 420000 3 struct hehe{ 4 int next; 5 int to; 6 int lca; 7 }; 8 hehe edge[N];//樹的鏈表 9 hehe qedge[N];//需要查詢LCA的兩節點的鏈表 10 int n,m,p,x,y; 11 int num_edge,num_qedge,head[N],qhead[N]; 12 int father[N]; 13 int visit[N];//判斷是否被找過 14 void add_edge(int from,int to){//建立樹的鏈表 15 edge[++num_edge].next=head[from]; 16 edge[num_edge].to=to; 17 head[from]=num_edge; 18 } 19 void add_qedge(int from,int to){//建立需要查詢LCA的兩節點的鏈表 20 qedge[++num_qedge].next=qhead[from]; 21 qedge[num_qedge].to=to; 22 qhead[from]=num_qedge; 23 } 24 int find(int z){//找爹函數 25 if(father[z]!=z) 26 father[z]=find(father[z]); 27 return father[z]; 28 } 29 int dfs(int x){//把整棵樹的一部分看作以節點x為根節點的小樹 30 father[x]=x;//由於節點x被看作是根節點,所以把x的father設為它自己 31 visit[x]=1;//標記為已被搜索過 32 for(int k=head[x];k;k=edge[k].next)//遍歷所有與x相連的節點 33 if(!visit[edge[k].to]){//若未被搜索 34 dfs(edge[k].to);//以該節點為根節點搞小樹 35 father[edge[k].to]=x;//把x的孩子節點的father重新設為x 36 } 37 for(int k=qhead[x];k;k=qedge[k].next)//搜索包含節點x的所有詢問 38 if(visit[qedge[k].to]){//如果另一節點已被搜索過 39 qedge[k].lca=find(qedge[k].to);//把另一節點的祖先設為這兩個節點的最近公共祖先 40 if(k%2)//由於將每一組查詢變為兩組,所以2n-1和2n的結果是一樣的 41 qedge[k+1].lca=qedge[k].lca; 42 else 43 qedge[k-1].lca=qedge[k].lca; 44 } 45 } 46 int main(){ 47 scanf("%d%d%d",&n,&m,&p);//輸入節點數,查詢數和根節點 48 for(int i=1;i<n;++i){ 49 scanf("%d%d",&x,&y);//輸入每條邊 50 add_edge(x,y); 51 add_edge(y,x); 52 } 53 for(int i=1;i<=m;++i){ 54 scanf("%d%d",&x,&y);//輸入每次查詢,考慮(u,v)時若查找到u但v未被查找,所以將(u,v)(v,u)全部記錄 55 add_qedge(x,y); 56 add_qedge(y,x); 57 } 58 dfs(p);//進入以p為根節點的樹的深搜 59 for(int i=1;i<=m;i++) 60 printf("%d ",qedge[i*2].lca);//兩者結果一樣,只輸出一組即可 61 return 0; 62 }
時間復雜度:O(n+m)。
離線算法。
