1. 程式人生 > >LCATarjan離線演算法

LCATarjan離線演算法

怎麼說離線演算法呢,我覺得就是說你把想要詢問的東西提前存起來了,這樣在遍歷然後順便建樹的過程中就可以隨時回答這個問題,然後再進行相應的更新…

#include<iostream>
using namespace std;
#include<vector>
#include<algorithm>
#include<string.h>
vector<int>node[40010];//存相鄰的點
vector<int>value[40010];//存相鄰的邊權
vector<int>query[40010];//存與i點相關的詢問
vector
<int>
num[40010];//存第幾次詢問 int vis[40010]; int f[40010]; int dis[40010];//dis陣列用來存每個點到根節點的距離,dis【i】表示i到根節點的距離 int pr[210];//存答案 int find(int x) { while (f[x] != x) x = f[x]; return x; } void join(int x, int y) { int fx = find(x); int fy = find(y); if (f[fx] != fy) f[fx] = fy; } void
TarjanLCA(int root, int weight) { f[root] = root; vis[root] = 1; dis[root] = weight; for (int i = 0; i < node[root].size(); i++) { int nextnode = node[root][i]; int nextw = value[root][i]; if (!vis[nextnode]) { TarjanLCA(nextnode, weight + nextw); join(nextnode, root); } } for
(int i = 0; i < query[root].size(); i++) { int neednode = query[root][i]; if (vis[neednode]) pr[num[root][i]] = dis[root] + dis[neednode] - 2 * dis[find(neednode)]; } } void init(int n) { memset(vis, 0, sizeof(vis)); memset(dis, 0, sizeof(dis)); memset(pr, 0, sizeof(pr)); for (int i = 1; i <= n; i++) { node[i].clear(); query[i].clear(); num[i].clear(); value[i].clear(); } } int main() { int t; cin >> t; while (t--) { int n, m; cin >> n >> m; init(n); for (int i = 0; i < n - 1; i++) { int u, v, w; cin >> u >> v >> w; node[u].push_back(v); node[v].push_back(u); value[u].push_back(w); value[v].push_back(w); } for (int i = 0; i < m; i++) { int u, v; cin >> u >> v; query[u].push_back(v);//存下詢問的兩個點 query[v].push_back(u); num[u].push_back(i);//存這是第幾次詢問了 num[v].push_back(i); } TarjanLCA(1, 0); for (int i = 0; i < m; i++) cout << pr[i]<<endl; } return 0; }

☝貼一份模板,帶權值的,當然我覺得LCA核心還是找最近公共祖先,權值是一個相當於副產品。

下面我自己模擬一下一個LCA的詢問,time to 胡言亂語。

假設我們有一組資料 9個節點 8條邊 聯通情況如下:1–2,1–3,2–4,2–5,3–6,5–7,5–8,7–9
設我們要查詢最近公共祖先的點為:9–8,4–6,7–5,5–3

下面我們開始跑程式。
輸入結束後,1的子節點有2,3;2的子節點1,4,5;3的子節點有1,6;4的子節點有2;5的子節點有2,7,8;6的子節點有3;7的子節點有5,9;8的子節點有5;9的子節點有7;

詢問的過程是8,9有關,4,6有關,7,5有關,5,3有關;

開始建樹,這裡的邊權都是0,先不管,從1開始,Tarjan(1,0)。f【1】=1,1標記訪問過,dis【1】=0;發現1有2個兒子,遍歷,先是2,發現2沒有被訪問過,然後進入Tarjan(2,0),f【2】=2,2標記訪問過,dis【2】=0;發現2有三個兒子,遍歷,先是1,結果1被訪問過,然後是4,發現4沒有被訪問過,進入Tarjan(4,0),f【4】=4,4標記被訪問過,然後發現4有一個兒子2已經被訪問過了,所以4尋找兒子的過程已經結束,進入詢問,問6,結果6沒有被訪問過,所以結束詢問,返回到Tarjan(4,0)執行完的地方,然後join(4,2),所以此時f【4】=2,繼續走,進入2的第二個兒子5,發現沒有被標記,進入Tarjan(5,0),f【5】=5,5被標記訪問過,5有兩個兒子,進入Tarjan(8,0),結束兒子訪問,進入詢問,與8有關的9還沒有被標記,所以結束,join(8,5),所以f【8】=5,然後繼續訪問5的兒子7,進入Tarjan(7,0),f【7】=7,7標記被訪問,發現7有一個未被標記的兒子9,然後進入Tarjan(9,0),f【9】=9,9標記為被訪問,然後9沒有有效兒子,進入詢問,9和8有關係,發現8被標記過,那麼8和9的最近公共祖先就是find(8)=5,因為這此時還是在這棵子樹中,讓我們繼續走看看,然後結束詢問join(9,7),此時f【9】=7,然後7的兒子都遍歷結束,進入詢問,7和5有關係,發現5已經被標記過了,所以7和5的最近公共祖先就是find(5)=5,詢問結束,然後返回到join(7,5),此時f【7】=5,5的兒子遍歷結束,然後就是詢問,詢問發現5和3有關係但是3還未被標記,所以繼續向前走,join(5,2),此時f【5】=2,然後發現2的兒子訪問結束,進入詢問,發現沒有詢問,join(2,1),此時f【2】=1,此時繼續訪問1的另一個兒子3,f【3】=3,3標記為訪問過,3有一個兒子6,進入Tarjan(6,0),f【6】=6,6標記為訪問過,然後繼續走發現6沒有有效兒子,然後進入詢問,詢問發現4,然後4被標記,那麼6和4的最近公共祖先就是find(4)=1,然後繼續走join(6,3),此時f【6】=3,然後3對兒子的訪問結束,進入詢問,發現5被訪問過,所以5和3的最近公共祖先就是find(5)=1,然後回到join(3,1),此時f【3】=1,然後發現對1的兒子的遍歷結束,進入詢問,發現沒有詢問,over!
所以我們的這些訪問出結果的順序是9-8,7-5,4-6,5-3,這其實就是利用的還是建樹的過程,在逐步建樹的過程中完成這些詢問,非常機智!
邊權的問題就是開一個dis陣列,存每一個點到根節點的距離,然後最後減去兩倍的dis【最近公共祖先】,dis陣列的取值是一個不斷累加的過程。

自己跑一遍程式就會好很多,而且我發現跑這種程式需要找一個合適大小的樹,太大比較複雜,太小也不好,看著簡單,但是體會不到演算法的過程和巧妙之處!