前提引入:
看到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;
}