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陣列的取值是一個不斷累加的過程。
自己跑一遍程式就會好很多,而且我發現跑這種程式需要找一個合適大小的樹,太大比較複雜,太小也不好,看著簡單,但是體會不到演算法的過程和巧妙之處!