最近公共祖先LCA Tarjan算法

這篇博客寫的非常不錯,我就是看這個學會的。

第一次寫最近公共祖先問題,用的鄰接表指針。

對於一棵有根樹,就會有父親結點,祖先結點,當然最近公共祖先就是這兩個點所有的祖先結點中深度最大的一個結點。

       0

       |

       1

     /   \

   2      3

比如說在這裏,如果0爲根的話,那麼1是2和3的父親結點,0是1的父親結點,0和1都是2和3的公共祖先結點,但是1纔是最近的公共祖先結點,或者說1是2和3的所有祖先結點中距離根結點最遠的祖先結點。

在求解最近公共祖先爲問題上,用到的是Tarjan的思想,從根結點開始形成一棵深搜樹,非常好的處理技巧就是在回溯到結點u的時候,u的子樹已經遍歷,這時候才把u結點放入合併集合中,這樣u結點和所有u的子樹中的結點的最近公共祖先就是u了,u和還未遍歷的所有u的兄弟結點及子樹中的最近公共祖先就是u的父親結點。以此類推。。這樣我們在對樹深度遍歷的時候就很自然的將樹中的結點分成若干的集合,兩個集合中的所屬不同集合的任意一對頂點的公共祖先都是相同的,也就是說這兩個集合的最近公共最先只有一個。對於每個集合而言可以用並查集來優化,時間複雜度就大大降低了,爲O(n + q),n爲總結點數,q爲詢問結點對數。

另外Tarjan解法,是一個離線算法,就是說它必須將所有詢問先記錄下來,再一次性的求出每個點對的最近公共祖先,只有這樣纔可以達到降低時間複雜度。另外還有一個在線算法,有待學習,呵呵。。

//parent爲並查集,FIND爲並查集的查找操作 //QUERY爲詢問結點對集合 //TREE爲基圖有根樹 Tarjan(u) visit[u] = true for each (u, v) in QUERY if visit[v] ans(u, v) = FIND(v) for each (u, v) in TREE if !visit[v] Tarjan(v) parent[v] = u

 

hdu2586 How far away ?

這道題題意是,給定一棵樹,每條邊都有一定的權值,q次詢問,每次詢問某兩點間的距離。這樣就可以用LCA來解,首先找到u, v 兩點的lca,然後計算一下距離值就可以了。這裏的計算方法是,記下根結點到任意一點的距離dis[],這樣ans = dis[u] + dis[v] - 2 * dis[lca(v, v)]了,這個表達式還是比較容易理解的。。

 

複製代碼
//============================================================================ // Name : hdu2586.cpp // Author : birdfly // Description : 最近公共祖先 //============================================================================ #include <iostream> #include <string.h> #include <stdio.h> #define NN 40002 // number of house #define MM 202 // number of query using namespace std; typedef struct node{ int v; int d; struct node *nxt; }NODE; NODE *Link1[NN]; NODE edg1[NN * 2]; // 樹中的邊 NODE *Link2[NN]; NODE edg2[NN * 2]; // 詢問的點對 int idx1, idx2, N, M; int res[MM][3]; // 記錄結果,res[i][0]: u res[i][1]: v res[i][2]: lca(u, v) int fat[NN]; int vis[NN]; int dis[NN]; void Add(int u, int v, int d, NODE edg[], NODE *Link[], int &idx){ edg[idx].v = v; edg[idx].d = d; edg[idx].nxt = Link[u]; Link[u] = edg + idx++; edg[idx].v = u; edg[idx].d = d; edg[idx].nxt = Link[v]; Link[v] = edg + idx++; } int find(int x){ // 並查集路徑壓縮 if(x != fat[x]){ return fat[x] = find(fat[x]); } return x; } void Tarjan(int u){ vis[u] = 1; fat[u] = u; for (NODE *p = Link2[u]; p; p = p->nxt){ if(vis[p->v]){ res[p->d][2] = find(p->v); // 存的是最近公共祖先結點 } } for (NODE *p = Link1[u]; p; p = p->nxt){ if(!vis[p->v]){ dis[p->v] = dis[u] + p->d; Tarjan(p->v); fat[p->v] = u; } } } int main() { int T, i, u, v, d; scanf("%d", &T); while(T--){ scanf("%d%d", &N, &M); idx1 = 0; memset(Link1, 0, sizeof(Link1)); for (i = 1; i < N; i++){ scanf("%d%d%d", &u, &v, &d); Add(u, v, d, edg1, Link1, idx1); } idx2 = 0; memset(Link2, 0, sizeof(Link2)); for (i = 1; i <= M; i++){ scanf("%d%d", &u, &v); Add(u, v, i, edg2, Link2, idx2); res[i][0] = u; res[i][1] = v; } memset(vis, 0, sizeof(vis)); dis[1] = 0; Tarjan(1); for (i = 1; i <= M; i++){ printf("%d\n", dis[res[i][0]] + dis[res[i][1]] - 2 * dis[res[i][2]]); } } return 0; }
複製代碼

 

poj1470  Closest Common Ancestors

這道題和上面那道一樣,很典型的LCA問題,不過讀入有點麻煩,求的是每個點被作爲最近公共祖先的次數,呵呵。。

 

複製代碼
//============================================================================ // Name : poj1470.cpp // Author : birdfly // Description : 最近公共祖先 //============================================================================ #include <iostream> #include <stdio.h> #include <string.h> #define NN 902 using namespace std; typedef struct node{ int v; struct node *nxt; }NODE; NODE edg1[NN * 2], edg2[NN * 1000];//數組要開大點 NODE *Link1[NN], *Link2[NN]; int idx1, idx2, N, M; int fat[NN]; int vis[NN]; int cnt[NN]; void Init(NODE *Link[], int &idx){ memset(Link, 0, sizeof(Link[0]) * (N + 1)); idx = 0; } void Add(int u, int v, NODE edg[], NODE *Link[], int & idx){ edg[idx].v = v; edg[idx].nxt = Link[u]; Link[u] = edg + idx++; edg[idx].v = u; edg[idx].nxt = Link[v]; Link[v] = edg + idx++; } int find(int x){ if(x != fat[x]){ return fat[x] = find(fat[x]); } return x; } void Tarjan(int u){ vis[u] = 1; fat[u] = u; for (NODE *p = Link2[u]; p; p = p->nxt){ if(vis[p->v]){ cnt[find(p->v)]++; } } for (NODE *p = Link1[u]; p; p = p->nxt){ if(!vis[p->v]){ Tarjan(p->v); fat[p->v] = u; } } } int main() { int i, u, v, n, root; int flag[NN]; while(scanf("%d", &N) != EOF){ Init(Link1, idx1); memset(flag, 0, sizeof(flag)); for (i = 1; i <= N; i++){ //數據的讀入方式很不錯啊 scanf("%d", &u); while(getchar() != '('); scanf("%d", &n); while(getchar() != ')'); while(n--){ scanf("%d", &v); flag[v] = 1; Add(u, v, edg1, Link1, idx1); } } scanf("%d", &M); Init(Link2, idx2); for (i = 1; i <= M; i++){ while(getchar() != '('); scanf("%d%d", &u, &v); while(getchar() != ')'); Add(u, v, edg2, Link2, idx2); } memset(vis, 0, sizeof(vis)); memset(cnt, 0, sizeof(cnt)); for (i = 1; i <= N; i++){// 第一個結點不一定是根結點 if(flag[i] == 0) break; } root = i; Tarjan(root); for (i = 1; i <= N; i++){ if(cnt[i]){ printf("%d:%d\n", i, cnt[i]); } } } return 0; }

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章