LCA算是OI中比较有用的算法了,下面来介绍LCA的三种大众求法。
暴力寻找法
这种是将一个点到根节点上所有的点都标记上,然后,有另一个点想根节点找,第一个有标记的便是LCA。这种的时间复杂度为O(n^2),算是比较慢的了。
上代码
#include<bits/stdc++.h>
using namespace std;
int n,m,s,ver[1000001],hed[1000001],nxt[1000001],tot=0,deep[500001],fa[500001],bj[500001],b[500001];
void add(int x,int y){
ver[++tot]=y,nxt[tot]=hed[x],hed[x]=tot;
}
void dfs(int x){
for(int i=hed[x];i;i=nxt[i]){
if(!b[ver[i]]){
b[ver[i]]=1;
fa[ver[i]]=x;
dfs(ver[i]);
}
}
}
int main(){
scanf("%lld%lld%lld",&n,&m,&s);
for(int i=1,x,y;i<n;i++){
scanf("%d%d",&x,&y);
add(x,y),add(y,x);
}
b[s]=1;
dfs(s);
for(int i=1,x,y;i<=m;i++){
memset(bj,0,sizeof(bj));
memset(b,0,sizeof(b));
scanf("%d%d",&x,&y);
bj[x]=1;
for(int i=fa[x];i;i=fa[i])
bj[i]=1;
if(bj[y]){
printf("%d\n",y);
continue;
}
for(int i=fa[y];i;i=fa[i])
if(bj[i]){
printf("%d\n",i);
break;
}
}
return 0;
}
二进制拆分法
这种是第一种的优化版,记录每个点的深度,若深度不同,根据二进制思想将其调至深度相同,然后二进制向上找,直至两点祖先一样,这点即为LCA。时间复杂度O(nlogn).
上代码
#include<bits/stdc++.h>
using namespace std;
int t,n,m,s,ver[1000001],hed[500001],nxt[1000001],tot=0,deep[500001],f[500001][20];
queue<int>q;
void add(int x,int y){
ver[++tot]=y,nxt[tot]=hed[x],hed[x]=tot;
}
void bfs(int a){
q.push(a),deep[a]=1;
while(q.size()){
int x=q.front();
q.pop();
for(int i=hed[x],y;i;i=nxt[i]){
if(deep[y=ver[i]]) continue;
deep[y]=deep[x]+1;
f[y][0]=x;
for(int j=1;j<=t;j++)
f[y][j]=f[f[y][j-1]][j-1];
q.push(y);
}
}
}
int lca(int x,int y){
if(deep[x]>deep[y])
swap(x,y);
for(int i=t;i>=0;i--)
if(deep[f[y][i]]>=deep[x])
y=f[y][i];
if(x==y) return x;
for(int i=t;i>=0;i--)
if(f[x][i]!=f[y][i])
x=f[x][i],y=f[y][i];
return f[x][0];
}
int main(){
scanf("%lld%lld%lld",&n,&m,&s);
for(int i=1,x,y;i<n;i++){
scanf("%d%d",&x,&y);
add(x,y),add(y,x);
}
t=(int)(log(n)/log(2))+1;
bfs(s);
for(int i=1,x,y;i<=m;i++){
scanf("%d%d",&x,&y);
printf("%d\n",lca(x,y));
}
}
LCA的Tarjan算法
这个算法的本质是使用并查集对第一种的优化,是一个离线算法。
总思路就是每进入一个节点u的深搜,就把整个树的一部分看作以节点u为根节点的小树,再搜索其他的节点。每搜索完一个点后,如果该点和另一个已搜索完点为需要查询LCA的点,则这两点的LCA为另一个点的现在的祖先。
1.先建立两个链表,一个为树的各条边,另一个是需要查询最近公共祖先的两节点。
2.建好后,从根节点开始进行一遍深搜。
3.先把该节点u的father设为他自己(也就是只看大树的一部分,把那一部分看作是一棵树),搜索与此节点相连的所有点v,如果点v没被搜索过,则进入点v的深搜,深搜完后把点v的father设为点u。
4.深搜完一点u后,开始判断节点u与另一节点v是否满足求LCA的条件,满足则将结果存入数组中。
5.搜索完所有点,自动退出初始的第一个深搜,输出结果。
时间复杂度为O(m+n)
上代码
#include<bits/stdc++.h>
using namespace std;
template<typename Type>inline void read(Type &xx){
Type f=1;char ch;xx=0;
for(ch=getchar();ch<'0'||ch>'9';ch=getchar())if(ch=='-')f=-1;
for(;ch>='0'&&ch<='9';ch=getchar())xx=xx*10+ch-'0';
xx*=f;
}
struct edge{
int to,next;
}e[1000001];
struct questions{
int to,next,same,num;
bool flag;
questions(){flag=false;}
}q[1000001];
bool b[500001];
int head[500001],que[500001],father[500001],n,m,s,nume=0,numq=0,ans[500001];
void add_edge(int x,int y){
e[++nume].to=y,e[nume].next=head[x];
head[x]=nume,e[++nume].to=x;
e[nume].next=head[y],head[y]=nume;
}
void add_que(int x,int y,int k){
q[++numq].to=y,q[numq].same=numq+1;
q[numq].next=que[x],q[numq].num=k;
que[x]=numq,q[++numq].to=x;
q[numq].same=numq-1,q[numq].next=que[y];
q[numq].num=k,que[y]=numq;
}
int find(int x){
if(father[x]!=x)father[x]=find(father[x]);
return father[x];
}
void unionn(int x,int y){
father[find(y)]=find(x);
}
void LCA(int point,int f){
for(int i=head[point];i!=0;i=e[i].next)
if(e[i].to!=f&&!b[e[i].to]){
LCA(e[i].to,point);
unionn(point,e[i].to);
b[e[i].to]=1;
}
for(int i=que[point];i!=0;i=q[i].next)
if(!q[i].flag&&b[q[i].to]){
ans[q[i].num]=find(q[i].to);
q[i].flag=1;
q[q[i].same].flag=1;
}
}
int main(){
read(n);read(m);read(s);
for(int i=1,x,y;i<=n-1;i++){
father[i]=i;
read(x);read(y);
add_edge(x,y);
}
father[n]=n;
for(int i=1,x,y;i<=m;i++){
read(x);read(y);
add_que(x,y,i);
}
LCA(s,0);
for(int i=1;i<=m;i++)
printf("%d\n",ans[i]);
return 0;
}