1. 程式人生 > >Tarjan離線演算法 (LCA最近公共祖先)

Tarjan離線演算法 (LCA最近公共祖先)

Tarjan離線演算法是利用並查集和DFS來達到離線處理的目的
我們都知道,對於一棵樹,後序遍歷一遍,訪問它的根的時機一定是後與它的孩子的。換一句話,當開始遍歷它的根節點的時候,它遍歷過的孩子的公共祖先一定是這個根而這也就成為了我們解題的思想。
由於是需要對整樹進行DFS,所以Tarjan需要在所有資訊都輸入完畢後才能進行操作,這也是為什麼要離線的原因

所謂線上演算法,就是每輸入一個查詢,就進行一次演算法操作,並輸出查詢結果。離線演算法,就是對所有的查詢,用一個表結構儲存起來,一次演算法處理掉所有的查詢,最後O(1)的複雜度輸出。

  1. 找到入度為0的點u(根節點)
  2. Tarjan(u)
    1. 訪問r的每個子結點v,如果該子結點v未被訪問,Tarjan(v)
    2. 將c加入到父親結點u中
    3. 標記v為已訪問
  3. 處理關於u的查詢,若查詢(u,v)中的v已遍歷過,則LCA(u,v)= v所在的集合的祖先,即Find(v);

    最後來一道例題吧
    POJ1470
    一道模板題,直接放程式碼了

#include <map>
#include <queue>
#include <cmath>
#include <cstdio>
#include <string>
#include <cstring>
#include <iostream>
#include <algorithm>
#define pb push_back #define lowbit(a) (a&(-a)) #define fori(a) for(int i=0;i<a;i++) #define forj(a) for(int j=0;j<a;j++) #define ifor(a) for(int i=1;i<=a;i++) #define jfor(a) for(int j=1;j<=a;j++) #define _mem(a,b) memset(a,0,(b+3)<<2) #define mem(a,b) memset(a,b,sizeof(a)) #define IN freopen("in.txt","r",stdin)
#define OUT freopen("out.txt","w",stdout) #define IO do{\ ios::sync_with_stdio(false);\ cin.tie(0);\ cout.tie(0);}while(0) using namespace std; typedef long long ll; const int maxn = 1e4+10; const int MAXN = 1e6+10; const int INF = 0x3f3f3f3f; const int inf = 0x3f; const double EPS = 1e-7; const double Pi = acos(-1); const int MOD = 1e9+7; vector <int >a[maxn]; //鄰接表存圖 vector <int >q[maxn]; //鄰接表存查詢 int p[maxn]; //並查集中的father陣列 bool v[maxn]; //是否被訪問 bool root[maxn]; //根節點 int res[maxn]; //答案陣列 int n; int Find(int x){ if(p[x]!=x) p[x] = Find(p[x]); return p[x]; } void join(int root1, int root2) { int x, y; x = Find(root1); y = Find(root2); if(x != y) p[y] = x; } void Tarjan(int u){ fori(a[u].size()) if(!v[a[u][i]]){ Tarjan(a[u][i]); join(u,a[u][i]); v[a[u][i]] = true; } fori(q[u].size()){ //對於所有查詢,如果i點已被訪問過,則能查詢其公共祖先 if(v[q[u][i]] == true) res[Find(q[u][i])]++; } } void Init(){ //初始化 fori(n+1){ a[i].clear(); q[i].clear(); p[i] = i; res[i] = 0; v[i] = 0; root[i] = 1; } } int main() { //IO; //IN; int bufx,bufy; int x(1); int t; while(cin >> n){ Init(); fori(n){ scanf("%d:(%d)",&bufx,&bufy); while(bufy--){ scanf("%d",&t); a[bufx].pb(t); root[t] = false; } } scanf("%d",&t); while(t--){ scanf(" (%d%d)",&bufx,&bufy); q[bufx].pb(bufy); q[bufy].pb(bufx); } ifor(n){ //找出根節點 if(root[i]){ Tarjan(i); break; } } ifor(n+1){ if(res[i]) cout << i<<":"<<res[i]<<endl; } } return 0; }