前提引入:
看到LCA這個詞的中文意思,顧名思義,也就是找兩個點最近的公共祖先罷了。
我相信,其實不看LCA的代碼,就自己寫,也能寫出LCA,因爲LCA算法實現就是兩個點一步一步往上走,找到同一個點就輸出結束
但這樣太慢了!O(n),我們需要優化!
所以智慧的科學家們發明出了許許多多玄♂ 學的優化方法,在這裏,我們需要掌握倍增(在線)和tarjan(離線)兩種優化方法
倍增:
顧名思義,倍增就是一步跳很多格,不一格一格跳了,而是一段一段地跳。怎樣利用倍增來求LCA呢?這裏我們先要做一些熱身準備:
1.d[i]:表示點i所在的層數
2.f[i][j]:表示i跳2的j次方後到達的點
3.f[i][j]=f[f[i][j-1]][j-1] 一個點跳兩段到達的地方等於先跳一段,再跳一段
瞭解了倍增現實所需要的數組和遞推式,我們就來實現它吧!
”””””””””””’
倍增三部曲:
1.dfs層數搜索曲
2.倍增預處理曲
3.lca登場尋找答案曲
”””””””””””’
我們以一道模版題來講解洛谷P3379模版題
#include <iostream>
#include <cstdio>
using namespace std;
//Statement_common
struct Edge{int next,to;}edge[500000*2+1];
int n,q,s,num_edge,x,y;
int head[500001],d[500001];
int f[500001][31];
//Statement_fun
int lca(int x,int y);
void dfs(int x);
void add_edge(int from,int to);
int read();
int main()
{
cin>>n>>q>>s;
for(int i=1;i<=n-1;i++)
{
x=read(),y=read();
add_edge(x,y),add_edge(y,x);
}
//1.dfs層數搜索曲
d[s]=1,f[s][0]=0;
dfs(s);
//2.倍增預處理曲
for(int j=1;j<=20;j++) //注意:最外層枚舉步數!
for(int i=1;i<=n;i++)
f[i][j]=f[f[i][j-1]][j-1];
//3.lca登場尋找答案曲
for(int i=1;i<=q;i++)
{
x=read(),y=read();
printf("%d\n",lca(x,y));
}
return 0;
}
int lca(int x,int y)
{
if(d[x]<d[y]) swap(x,y);//人爲規定x層數深於y
//以下一重循環把x提到與y同一層
for(int i=20;i>=0;i--)
if(d[f[x][i]]>=d[y])
x=f[x][i];
if(x==y) return x;//如果相等,不用再找了
//以下一重循環找lca
for(int i=20;i>=0;i--)
if(f[x][i]!=f[y][i])
x=f[x][i],y=f[y][i];
return f[x][0];//因爲最後找到的是它們的lca的下一層,所以要輸出爸爸
}
void dfs(int x)//dfs找層數不解釋
{
for(int i=head[x];i!=0;i=edge[i].next)
if(!d[edge[i].to])
{
d[edge[i].to]=d[x]+1;//由點x拓展出來的點的層數肯定是x層數+1
f[edge[i].to][0]=x;//dfs時順便找出兩點間關係
dfs(edge[i].to);
}
}
void add_edge(int from,int to)
{
edge[++num_edge].next=head[from];
edge[num_edge].to=to;
head[from]=num_edge;
}
int read()
{
int x=0; char c=getchar();
while(c<'0' || c>'9') c=getchar();
while(c>='0' && c<='9') {x=x*10+c-'0'; c=getchar();}
return x;
}