最近公共祖先LCA:Tarjan算法

1,並查集+dfs
對整個樹進行深度優先遍歷,並在遍歷的過程中不斷地把一些目前可能查詢到的並且結果相同的節點用並查集合並.

2,分類,使每個結點都落到某個類中,到時候只要執行集合查詢,就可以知道結點的LCA了。
對於一個結點u.類別有:
以u爲根的子樹、除類一以外的以f(u)爲根的子樹、除前兩類以外的以f(f(u))爲根的子樹、除前三類以外的以f(f(f(u)))爲根的子樹……

類一的LCA爲u,類二爲f(u),類三爲f(f(u)),類四爲f(f(f(u)))。這樣的分類看起來好像並不困難。
但關鍵是查詢是二維的,並沒有一個確定的u。接下來就是這個算法的巧妙之處了。
利用遞歸的LCA過程。
當lca(u)執行完畢後,以u爲根的子樹已經全部併爲了一個集合。而一個lca的內部實際上做了的事就是對其子結點,依此調用lca.
當v1(第一個子結點)被lca,正在處理v2的時候,以v1爲根的子樹+u同在一個集合裏,f(u)+編號比u小的u的兄弟的子樹 同在一個集合裏,f(f(u)) + 編號比f(u)小的 f(u)的兄弟 的子樹 同在一個集合裏…… 
而這些集合,對於v2的LCA都是不同的。因此只要查詢x在哪一個集合裏,就能知道LCA(v2,x)

還有一種可能,x不在任何集合裏。當他是v2的兒子,v3,v4等子樹或編號比u大的u的兄弟的子樹(等等)時,就會發生這種情況。即還沒有被處理。還沒有處理過的怎麼辦?把一個查詢(x1,x2)往查詢列表裏添加兩次,一次添加到x1的列表裏,一次添加到x2的列表裏,如果在做x1的時候發現 x2已經被處理了,那就接受這個詢問。(兩次中必定只有一次詢問被接受).

3,應用:http://acm.pku.edu.cn/JudgeOnline/problem?id=1330
實現代碼:
Cpp代碼 複製代碼 收藏代碼
  1. #include<iostream>  
  2. #include<vector>  
  3. using namespace std;  
  4.   
  5. const int MAX=10001;  
  6. int f[MAX];  
  7. int r[MAX];  
  8. int indegree[MAX];//保存每個節點的入度  
  9. int visit[MAX];  
  10. vector<int> tree[MAX],Qes[MAX];  
  11. int ancestor[MAX];  
  12.   
  13.   
  14. void init(int n)  
  15. {  
  16.     for(int i=1;i<=n;i++)  
  17.     {  
  18.   
  19.         r[i]=1;  
  20.         f[i]=i;  
  21.         indegree[i]=0;  
  22.         visit[i]=0;  
  23.         ancestor[i]=0;  
  24.         tree[i].clear();  
  25.         Qes[i].clear();  
  26.     }  
  27.   
  28. }  
  29.   
  30. int find(int n)  
  31. {  
  32.     if(f[n]==n)  
  33.         return n;  
  34.     else  
  35.         f[n]=find(f[n]);  
  36.     return f[n];  
  37. }//查找函數,並壓縮路徑  
  38.   
  39. int Union(int x,int y)  
  40. {  
  41.     int a=find(x);  
  42.     int b=find(y);  
  43.     if(a==b)  
  44.         return 0;  
  45.     //相等的話,x向y合併  
  46.     else if(r[a]<=r[b])  
  47.     {  
  48.         f[a]=b;  
  49.         r[b]+=r[a];  
  50.     }  
  51.     else  
  52.     {  
  53.         f[b]=a;  
  54.         r[a]+=r[b];  
  55.     }  
  56.     return 1;  
  57.   
  58. }//合併函數,如果屬於同一分支則返回0,成功合併返回1  
  59.   
  60.   
  61. void LCA(int u)  
  62. {  
  63.     ancestor[u]=u;  
  64.     int size = tree[u].size();  
  65.     for(int i=0;i<size;i++)  
  66.     {  
  67.         LCA(tree[u][i]);  
  68.         Union(u,tree[u][i]);  
  69.         ancestor[find(u)]=u;  
  70.     }  
  71.     visit[u]=1;  
  72.     size = Qes[u].size();  
  73.     for(int i=0;i<size;i++)  
  74.     {  
  75.         //如果已經訪問了問題節點,就可以返回結果了.  
  76.         if(visit[Qes[u][i]]==1)  
  77.         {  
  78.             cout<<ancestor[find(Qes[u][i])]<<endl;  
  79.             return;  
  80.         }  
  81.     }  
  82. }  
  83.   
  84.   
  85. int main()  
  86. {  
  87.     int cnt;  
  88.     int n;  
  89.     cin>>cnt;  
  90.     while(cnt--)  
  91.     {  
  92.         cin>>n;;  
  93.         init(n);  
  94.         int s,t;  
  95.         for(int i=1;i<n;i++)  
  96.         {  
  97.             cin>>s>>t;  
  98.             tree[s].push_back(t);  
  99.             indegree[t]++;  
  100.         }  
  101.         //這裏可以輸入多組詢問  
  102.         cin>>s>>t;  
  103.         //相當於詢問兩次  
  104.         Qes[s].push_back(t);  
  105.         Qes[t].push_back(s);  
  106.         for(int i=1;i<=n;i++)  
  107.         {  
  108.             //尋找根節點  
  109.             if(indegree[i]==0)  
  110.             {  
  111.                 LCA(i);  
  112.                 break;  
  113.             }  
  114.         }  
  115.     }  
  116.     return 0;  
  117. }  
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章