定义
LCA(Least Common Ancestors),即最近公共祖先,是指在有根树中,找出某两个结点u和v最近的公共祖先。
解决方案
最简单的LCA问题即给你一棵
n
个节点的树,
朴素算法
一般能想到的最暴力的方法就是:对于两个节点x,y,不断把深度较大的一个往上移,直到两个点相遇。
但是显然这样模拟是非常慢的,复杂度可以达到 O(n∗q) 。
于是我们可以采取以下两种方法。
DFS–ST算法(在线)
算法思想
既然一个一个移这么慢,有没有什么办法能移得快一点呢?答案当然是肯定的。
用ST表啊(没学过不要紧,我也是先学LCA再学ST的)!倍增多快对不对!
算法实现
类似的,定义
fa[i][j]
表示
i
的
而对于其它的
对于 fa[i][j](j>0) ,有如下递推式(仔细yy一下):
fa[i][j]=fa[fa[i][j-1]][j-1];
然后就可以一下子跳一大步啦!
时间复杂度 O(log2n∗(n+q))
代码实现:
void dfs(int x,int dep){//先DFS记录深度与fa[i][0]
depth[x]=dep;
for (int i=h[x];i;i=ed[i].next)//邻接表
if (ed[i].to!=fa[x][0]){
fa[ed[i].to][0]=x;
dfs(ed[i].to,dep+1);
}
}
void make(){//递推
for (int j=1;j<=19;j++)//j要放在最外层
for (int i=1;i<=n;i++)
fa[i][j]=fa[fa[i][j-1]][j-1];//递推式
}
int LCA(int x,int y){//寻找LCA
if (depth[x]<depth[y]) swap(x,y);
for (int i=19;i>=0;i--)//一定要先跳大步,不然会跳漏掉,下同
if (depth[fa[x][i]]>=depth[y]) x=fa[x][i];//先把x与y调到同一深度
if (x==y) return x;//如果恰好相等,直接返回
for (int i=19;i>=0;i--)//两个一起跳,跳到两者的父亲一样为止
if (fa[x][i]!=fa[y][i]){
x=fa[x][i]; y=fa[y][i];
}
return fa[x][0];
}
Tarjan算法(离线)
算法思想:
有些时候题目允许你离线操作,这时便可以采用另一种方法:Tarjan。
Tarjan其实就是一个DFS的过程,对于节点
x
,把它的子树与它进行合并(并查集维护)。再遍历
根据DFS的性质,如果
y
被搜完了,那么
可能解释的不是很清楚,读者们可以画画图体会一下我太懒啦
时间复杂度 O(n+q)
算法实现
具体实现只需要按照这几个步骤即可:
①:从根节点开始遍历(DFS)。
②:遍历节点x的子节点,并标记其为已访问。
③:把子节点合并到x上。
④:遍历x的询问,如果发现y已标记,则该询问的答案即为findfather(y)。
代码:
int findfather(int x){//并查集查询+路压
if (fa[x]==x) return x;
return fa[x]=findfather(fa[x]);
}
void Tarjan(int x){
f[x]=true; fa[x]=x;//标记
for (int i=h[x];i;i=ed[i].next)//遍历子节点
if (!f[ed[i].to]){
Tarjan(ed[i].to); fa[ed[i].to]=x;
}
for (int i=h1[x];i;i=ed[i].next)//遍历询问
if (f[ed[i].to]) ans[ed[i].num]=findfather(ed[i].to);
}
模板
以洛谷P3379为例:
先放两张图感受下ST与Tarjan的快慢
ST:
Tarjan:
差距自行体会(两者均加了读优)
ST代码:
#include<cstdio>
#include<cstring>
#include<algorithm>
#define MAXN 500000
using namespace std;
struct edge{
int next,to;
};
int n,s,q,k;
int fa[MAXN+5][20],h[MAXN+5],depth[MAXN+5];
edge ed[MAXN*2+5];
inline char readc(){//读优
static char buf[100000],*l=buf,*r=buf;
if (l==r) r=(l=buf)+fread(buf,1,100000,stdin);
if (l==r) return EOF; return *l++;
}
inline int _read(){
int num=0; char ch=readc();
while (ch<'0'||ch>'9') ch=readc();
while (ch>='0'&&ch<='9') { num=num*10+ch-48; ch=readc(); }
return num;
}
void addedge(int x,int y){
ed[++k].next=h[x]; ed[k].to=y; h[x]=k;
}
void dfs(int x,int dep){
depth[x]=dep;
for (int i=h[x];i;i=ed[i].next)
if (ed[i].to!=fa[x][0]){
fa[ed[i].to][0]=x;
dfs(ed[i].to,dep+1);
}
}
void make(){
for (int j=1;j<=19;j++)
for (int i=1;i<=n;i++)
fa[i][j]=fa[fa[i][j-1]][j-1];
}
int LCA(int x,int y){
if (depth[x]<depth[y]) swap(x,y);
for (int i=19;i>=0;i--)
if (depth[fa[x][i]]>=depth[y]) x=fa[x][i];
if (x==y) return x;
for (int i=19;i>=0;i--)
if (fa[x][i]!=fa[y][i]){
x=fa[x][i]; y=fa[y][i];
}
return fa[x][0];
}
int main(){
n=_read(); q=_read(); s=_read();
for (int i=1;i<n;i++){
int u=_read(),v=_read();
addedge(u,v); addedge(v,u);
}
fa[s][0]=s;
dfs(s,0);
make();
while (q--){
int u=_read(),v=_read();
printf("%d\n",LCA(u,v));
}
return 0;
}
Tarjan代码:
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
#define MAXN 500000
using namespace std;
struct edge{
int next,to,num;
};
int n,s,q,k;
int fa[MAXN+5],h[MAXN+5],ans[MAXN+5],h1[MAXN+5];
edge ed[MAXN*4+5];
bool f[MAXN+5];
inline char readc(){
static char buf[100000],*l=buf,*r=buf;
if (l==r) r=(l=buf)+fread(buf,1,100000,stdin);
if (l==r) return EOF; return *l++;
}
inline int _read(){
int num=0; char ch=readc();
while (ch<'0'||ch>'9') ch=readc();
while (ch>='0'&&ch<='9') { num=num*10+ch-48; ch=readc(); }
return num;
}
void addedge(int x,int y,int *h,int num){
ed[++k].next=h[x]; ed[k].to=y; ed[k].num=num; h[x]=k;
}
int findfather(int x){
if (fa[x]==x) return x;
return fa[x]=findfather(fa[x]);
}
void Tarjan(int x){
f[x]=true; fa[x]=x;
for (int i=h[x];i;i=ed[i].next)
if (!f[ed[i].to]){
Tarjan(ed[i].to); fa[ed[i].to]=x;
}
for (int i=h1[x];i;i=ed[i].next)
if (f[ed[i].to]) ans[ed[i].num]=findfather(ed[i].to);
}
int main(){
n=_read(); q=_read(); s=_read();
for (int i=1;i<n;i++){
int u=_read(),v=_read();
addedge(u,v,h,0); addedge(v,u,h,0);
}
for (int i=1;i<=q;i++){
int u=_read(),v=_read();
addedge(u,v,h1,i); addedge(v,u,h1,i);
}
Tarjan(s);
for (int i=1;i<=q;i++) printf("%d\n",ans[i]);
return 0;
}