LCA
最近公共祖先_LCA
给定一棵有根树,若节点z即可以到x也可以到y,则称z为xy的公共祖先,公共祖先中深度最大(即距离x,y最近)的则是xy最近公共祖先,称为LCA(x,y)。
以洛谷的P3379【模板】最近公共祖先(LCA)进行讲解:
题目描述
如题,给定一棵有根多叉树,请求出指定两个点直接最近的公共祖先。
输入格式
第一行包含三个正整数 N,M,SN,M,S,分别表示树的结点个数、询问的个数和树根结点的序号。
接下来 N-1N−1 行每行包含两个正整数 x, yx,y,表示 xx 结点和 yy 结点之间有一条直接连接的边(数据保证可以构成树)。
接下来 MM 行每行包含两个正整数 a, ba,b,表示询问 aa 结点和 bb 结点的最近公共祖先。
输出格式
输出包含 MM 行,每行包含一个正整数,依次为每一个询问的结果。
输入输出样例
输入 #1
5 5 4
3 1
2 4
5 1
1 4
2 4
3 2
3 5
1 2
4 5
输出 #1
4
4
1
4
4
说明/提示
对于 30%的数据,N\leq 10N≤10,M\leq 10M≤10。
对于 70%的数据,N\leq 10000N≤10000,M\leq 10000M≤10000。
对于 100% 的数据,N\leq 500000N≤500000,M\leq 500000M≤500000。
样例说明:
该树结构如下:
第一次询问:2, 42,4 的最近公共祖先,故为 44。
第二次询问:3, 23,2 的最近公共祖先,故为 44。
第三次询问:3, 53,5 的最近公共祖先,故为 11。
第四次询问:1, 21,2 的最近公共祖先,故为 44。
第五次询问:4, 54,5 的最近公共祖先,故为 44。
故输出依次为 4, 4, 1, 4, 44,4,1,4,4。
向上标记法\(O(mn)\)
在第一次dfs时,记录每个节点的pre。
对于每个询问的x,y,先通过pre将两者调成到深度相同的状态,再向上调整,指导x'==y'。
时间复杂度为\(O(mn)\)。本题会TLE3个点。
#include<bits/stdc++.h>
using namespace std;
const int N=5e5+5;
int n,m,root;
int te,tail[N],pre[N],dep[N];
struct e_
{
int v,pre;
}e[N*2];
inline void add(int u,int v)
{
e[++te]=(e_){v,tail[u]};
tail[u]=te;
}
void dfs(int u)
{
for(int i=tail[u];i;i=e[i].pre)
{
int v=e[i].v;
if(dep[v]) continue;
pre[v]=u;
dep[v]=dep[u]+1;
dfs(v);
}
}
int ask(int x,int y)
{
if(dep[x]<dep[y]) swap(x,y);
while(dep[x]>dep[y]) x=pre[x];
while(x!=y) x=pre[x],y=pre[y];
return x;
}
int main()
{
scanf("%d %d %d",&n,&m,&root);
for(int i=1,u,v;i<n;++i)
{
scanf("%d %d",&u,&v);
add(u,v);
add(v,u);
}
dep[root]=1;
dfs(root);
for(int i=1,x,y;i<=m;++i)
{
scanf("%d %d",&x,&y);
printf("%d\n",ask(x,y));
}
}
树上倍增法\(O(mlogn)\)
对于每个节点,\(F[x,k]\)储存x第2k个祖先。
时间复杂度为\(O(mlogn)\)。本题可AC。
#include<bits/stdc++.h>
using namespace std;
const int N=5e5+5;
int n,m,root;
int te,tail[N],f[N][25],dep[N];
struct e_
{
int v,pre;
}e[N*2];
inline void add(int u,int v)
{
e[++te]=(e_){v,tail[u]};
tail[u]=te;
}
void dfs(int u)
{
for(int i=tail[u];i;i=e[i].pre)
{
int v=e[i].v;
if(dep[v]) continue;
dep[v]=dep[u]+1;
f[v][0]=u;
for(int j=1;(1<<j)<dep[v];++j) f[v][j]=f[f[v][j-1]][j-1];
dfs(v);
}
}
int ask(int u,int v)
{
if(dep[u]<dep[v]) swap(u,v);
for(int i=20;i>=0;--i)
if(dep[v]+(1<<i)<=dep[u]) u=f[u][i];
if(u==v) return u;
for(int i=20;i>=0;--i)
if(f[u][i]!=f[v][i]) u=f[u][i],v=f[v][i];
return f[u][0];
}
int main()
{
scanf("%d %d %d",&n,&m,&root);
for(int i=1,u,v;i<n;++i)
{
scanf("%d %d",&u,&v);
add(u,v);
add(v,u);
}
dep[root]=1;
dfs(root);
for(int i=1,x,y;i<=m;++i)
{
scanf("%d %d",&x,&y);
printf("%d\n",ask(x,y));
}
}
Tarjan\(O(n)\)
离线储存所有需要查询的(x,y)。
对于DFS到每个节点时,有三种状态,一种为已经回溯了,一种是递推但未回溯的,一种是尚未递推的。
对于第一种状态的节点,我们采用并查集将它并到第二种状态的点里,即f[y]=相连的第一个尚未回溯的点,则对于x和这部分点的最近公共祖先为f[y]。
对于第二种状态的节点,在递归回溯后也可转移为第一种状态的情况。
对于第三种状态的节点,在递归到第三种状态的点时,现在的节点x则为一二状态,仍可转移到第一种状态的情况。
时间复杂度为O(n)。本题可AC。
#include<bits/stdc++.h>
using namespace std;
const int N=5e5+5;
int n,m,root;
int te,tt,tail[N],tail2[N],f[N],zt[N];
struct e_
{
int v,pre;
}e[N*2];
struct t_
{
int u,v,val,preu,prev;
}t[N];
inline void add(int u,int v)
{
e[++te]=(e_){v,tail[u]};
tail[u]=te;
}
inline void add_(int u,int v)
{
t[++tt]=(t_){u,v,0,tail2[u],tail2[v]};
tail2[u]=tail2[v]=tt;
}
int find(int x)
{
return f[x]!=x?f[x]=find(f[x]):x;
}
void dfs(int u,int fa)
{
f[u]=u;
zt[u]=1;
for(int i=tail[u];i;i=e[i].pre)
{
int v=e[i].v;
if(v==fa) continue;
dfs(v,u);
}
for(int i=tail2[u];i;i=t[i].u==u?t[i].preu:t[i].prev)
{
int v=t[i].u==u?t[i].v:t[i].u;
if(!t[i].val&&zt[v]==2) t[i].val=find(v);
}
f[u]=fa;
zt[u]=2;
}
int main()
{
scanf("%d %d %d",&n,&m,&root);
for(int i=1,u,v;i<n;++i)
{
scanf("%d %d",&u,&v);
add(u,v);
add(v,u);
}
for(int i=1,u,v;i<=m;++i)
{
scanf("%d %d",&u,&v);
add_(u,v);
}
dfs(root,-1);
for(int i=1;i<=m;++i)
printf("%d\n",t[i].val);
}

浙公网安备 33010602011771号