LCA是很早之前听说过的,但是一直没学,看到一个网络流的题目设计到LCA所以今天就学习了一下。
求两个节点的公共祖先(即求深度最深的祖先)
1.tarjan算法
#include<iostream>
#include<cstdio>
#include<vector>
using namespace std;
#define N 10005
int in[N],vis[N],fa[N];
int flag,n,qu,qv;
vector<int>V[N];
void init()
{
for(int i=1;i<=n;i++)
{
V[i].clear();
in[i]=0;
fa[i]=i;
vis[i]=0;
}
flag=0;
}
int Find(int x)
{
return fa[x]==x?x:fa[x]=Find(fa[x]);
}
void U(int x,int y)
{
int f1=Find(x);
int f2=Find(y);
fa[f1]=f2;
}
void Tarjan(int u)
{
//cout<<u<<endl;
if(flag)return ;
for(int i=0;i<V[u].size();i++)
{
int v=V[u][i];
Tarjan(v);
U(v,u);
vis[v]=1;
}
if(flag) return ;
if(u==qu && vis[qv])
{
printf("%d\n",Find(qv));
flag=1;
}
if(u==qv && vis[qu])
{
printf("%d\n",Find(qu));
flag=1;
}
return ;
}
int main()
{
int t;
cin>>t;
while(t--)
{
scanf("%d",&n);
init();
for(int i=1;i<=n-1;i++)
{
int u,v;
scanf("%d%d",&u,&v);
in[v]++;
V[u].push_back(v);
}
scanf("%d%d",&qu,&qv);
for(int i=1;i<=n;i++)
{
if(in[i]==0)
{
Tarjan(i);
break;
}
}
}
}
离线的tarjan算法是利用了dfs+并查集
1.先对树进行深度便利
2.如果当前节点没有子节点则返回
3.标记当前节点,并且把当前节点和他的父节点放进一个集合中
4.遍历包含该点的问题,如果另一个点被标记的话则最近公共祖先就是当前节点的父节点(这里指已经压缩过路径的父节点)。
2.在线的倍增算法
#include<iostream>
#include<cstdio>
#include<vector>
#include<cstring>
using namespace std;
#define N 500005
struct node
{
int to,nt;
}g[N*2];
int tot;
int head[N];
void addedg(int x,int y)
{
g[tot].to=y;
g[tot].nt=head[x];
head[x]=tot++;
g[tot].to=x;
g[tot].nt=head[y];
head[y]=tot++;
}
int dep[N];
int f[N][21];
void dfs(int u,int fa)
{
dep[u]=dep[fa]+1;
f[u][0]=fa;
for(int i=head[u];i!=-1;i=g[i].nt)
{
int to=g[i].to;
if(to!=fa)
dfs(to,u);
}
}
int Lca(int a,int b)
{
if(dep[a]<dep[b])swap(a,b);
for(int i=20;i>=0;i--)
{
if(dep[a]-(1<<i)>=dep[b])
{
a=f[a][i];
}
}
if(a==b)return a;
for(int i=20;i>=0;i--)
{
if(f[a][i]!=f[b][i])
{
a=f[a][i];
b=f[b][i];
}
}
return f[a][0];
}
int main()
{
int n,m,s;
cin>>n>>m>>s;
memset(head,-1,sizeof(head));
int x,y;
for(int i=1;i<n;i++)
{
scanf("%d%d",&x,&y);
addedg(x,y);
}
dfs(s,0);
for(int i=1;i<=20;i++)
{
for(int j=1;j<=n;j++)
{
int p=f[j][i-1];
f[j][i]=f[p][i-1];
}
}
for(int i=1;i<=m;i++)
{
scanf("%d%d",&x,&y);
printf("%d\n",Lca(x,y));
}
return 0;
}
算法流程:
1.首先对当前的树进行深度优先遍历并且给每个节点标上深度并且记录当前节点往上走2的i次幂时的父节点
2.注意遍历双向边的树时不要死循环。
3.利用上边的到的已知信息(即每个节点向上走深度为1时的父节点),我们利用倍增的思想得到当前节点向上走2的i(1-n)时的父节点。
4.即f[a][i]=f[f[a][i-1]][i-1];
5.然后利用这些信息我们就可以在logn的时间内找到最近公共祖先。
6.我们先让a点达到与b点同样的深度(这里假设a的深度大于b的深度),这个过程利用的性质是任何一个数(这里是深度差)
可以由若干个不同的2的i次幂组成(需注意的是要倒着来)。
7.达到同一深度之后就继续利用刚才所说的性质 (其实还是慢慢找到父节点相同的点)。