最近公共祖先( L C A LCA LCA, L o w e s t C o m m o n A n C e s t o r Lowest Common AnCestor LowestCommonAnCestor)
在一棵树形结构中,有两个节点 x x x 和 y y y 的公共祖先中深度最大的一个。
L C A LCA LCA的实现
方法1:暴力染色
1.从
x
x
x 网上走到根节点,并沿途染色。
2从
y
y
y 往上走到根节点,沿途遇到的第一个染色的第一个染色节点即为
x
x
x 和
y
y
y 的,记为
l
c
a
(
x
,
y
)
lca(x,y)
lca(x,y)。
时间复杂度
O
(
N
)
O(N)
O(N)。
方法2:倍增求 L C A LCA LCA
第一大步:预处理
d
p
[
i
]
[
j
]
dp[i][j]
dp[i][j] 表示点
i
i
i 线上走
2
2
2 的
j
j
j 次方步到达的结点编号。
d
p
[
i
]
[
j
]
=
d
p
[
d
p
[
i
]
[
j
−
1
]
]
[
j
−
1
]
dp[i][j]=dp[ dp[i][j-1] ][j-1]
dp[i][j]=dp[dp[i][j−1]][j−1]
初始状态:
d
p
[
i
]
[
0
]
=
f
a
[
i
]
dp[i][0]=fa[i]
dp[i][0]=fa[i]
时间复杂度预处理
O
(
N
)
O(N)
O(N),调用 $O(log N) $。
第二大步:询问一对
(
x
,
y
)
(x,y)
(x,y) 的
L
C
A
LCA
LCA。
1.约定点
y
y
y 的深度更大,若
d
e
p
[
y
]
<
d
e
p
[
x
]
dep[y]<dep[x]
dep[y]<dep[x] ,则
s
w
a
p
(
x
,
y
)
;
swap(x,y);
swap(x,y);
2.点
y
y
y 向上倍增跳跃至于
x
x
x 的深度一致,并判断是否重合。
3.
x
x
x 和
y
y
y 一起向上倍增跳跃直到两者相等。
L
C
A
LCA
LCA 的应用:
1.求树上两点之间的距离
d
i
s
[
x
]
+
d
i
s
[
y
]
−
2
∗
d
i
s
[
l
c
a
(
x
,
y
)
]
dis[x]+dis[y]-2*dis[lca(x,y)]
dis[x]+dis[y]−2∗dis[lca(x,y)],其中地上
d
i
s
dis
dis 表示根节点到点
x
x
x 的距离
其他方法由于没有学,并没有写在这里,多请谅解。
//预处理
void pre_lca(int cur,int fa)//cur的父节点是fa
{
dp[cur][0]=fa;//边界条件
dep[cur]=dep[fa]+1;//深度
for(int i=1;(1<<i)<=dep[cur];i++)//指数
dp[i][j]=dp[dp[i][j-1]][j-1];
for(int i=0;i<v[x].size();i++)//循环cur的相邻节点
{
int nxt=v[cur][i];
if(nxt!=fa)
pre_lca(nxt,cur);
}
}
void lca(int x,int y)//x和y的最近公共祖先
{
if(dep[x]>dep[y])
swap(x,y);
for(int i=20;i>=0;i--)
if(dep[dp[y][i]]>=dep[x])//让y上跳与x保持一致
y=dp[y][i];//y跳
if(x==y)
return x;
for(int i=20;i>=0;i--)//x和y一起往上跳
if(dp[x][i]!=dp[y][i])
x=dp[x][i],y=dp[y][i];
return dp[x][0];
}
实践
P3379 【模板】最近公共祖先(LCA)
接着就可以AC P3379 【模板】最近公共祖先 LCA了。
#include<bits/stdc++.h>
using namespace std;
const int N=5e5+5;
int n,m,s,a[N],dp[N][25],dep[N];
vector<int>v[N];
void pre_lca(int cur,int fa)
{
dp[cur][0]=fa;
dep[cur]=dep[fa]+1;
for(int i=1;(1<<i)<=dep[cur];i++)
dp[cur][i]=dp[dp[cur][i-1]][i-1];
for(int i=0;i<v[cur].size();i++)
{
int nxt=v[cur][i];
if(nxt!=fa)
pre_lca(nxt,cur);
}
}
int lca(int x,int y)
{
if(dep[x]>dep[y])
swap(x,y);
for(int i=20;i>=0;i--)
if(dep[dp[y][i]]>=dep[x])
y=dp[y][i];
if(x==y)
return x;
for(int i=20;i>=0;i--)
if(dp[x][i]!=dp[y][i])
x=dp[x][i],y=dp[y][i];
return dp[x][0];
}
int main()
{
cin>>n>>m>>s;
for(int i=1;i<n;i++)
{
int x,y;
cin>>x>>y;
v[x].push_back(y);
v[y].push_back(x);
}
pre_lca(s,0);
while(m--)
{
int x,y;
cin>>x>>y;
cout<<lca(x,y)<<'\n';
}
return 0;
}
1552:【例 1】点的距离
这道题目由于没有边权,所以很好做, x x x 和 y y y 的距离只需要通过 d e p [ x ] + d e p [ y ] − 2 ∗ d e p [ l c a ( x , y ) ] dep[x]+dep[y]-2*dep[lca(x,y)] dep[x]+dep[y]−2∗dep[lca(x,y)] 就能得到。
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+5;
int n,m,s,a[N],dp[N][25],dep[N];
vector<int>v[N];
void pre_lca(int cur,int fa)
{
dp[cur][0]=fa;
dep[cur]=dep[fa]+1;
for(int i=1;(1<<i)<=dep[cur];i++)
dp[cur][i]=dp[dp[cur][i-1]][i-1];
for(int i=0;i<v[cur].size();i++)
{
int nxt=v[cur][i];
if(nxt!=fa)
pre_lca(nxt,cur);
}
}
int lca(int x,int y)
{
if(dep[x]>dep[y])
swap(x,y);
for(int i=20;i>=0;i--)
if(dep[dp[y][i]]>=dep[x])
y=dp[y][i];
if(x==y)
return x;
for(int i=20;i>=0;i--)
if(dp[x][i]!=dp[y][i])
x=dp[x][i],y=dp[y][i];
return dp[x][0];
}
int main()
{
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
cin>>n;
for(int i=1;i<n;i++)
{
int x,y;
cin>>x>>y;
v[x].push_back(y);
v[y].push_back(x);
}
pre_lca(1,0);
cin>>m;
while(m--)
{
int x,y;
cin>>x>>y;
cout<<dep[x]+dep[y]-2*dep[lca(x,y)]<<'\n';
}
return 0;
}
1556:Dis
这道题在前一体的基础上也很容易,只需要在初始化 L C A LCA LCA 时将 d i s [ i ] dis[i] dis[i] 维护一下, d i s [ i ] dis[i] dis[i] 表示第 i i i 个点到树根的距离。
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+5;
struct edge{
int to,w;
};
int n,m,s,a[N],dp[N][25],dep[N],dis[N];
vector<edge>v[N];
void pre_lca(int cur,int fa)
{
dp[cur][0]=fa;
dep[cur]=dep[fa]+1;
for(int i=1;(1<<i)<=dep[cur];i++)
dp[cur][i]=dp[dp[cur][i-1]][i-1];
for(int i=0;i<v[cur].size();i++)
{
int nxt=v[cur][i].to,w=v[cur][i].w;
if(nxt!=fa)
{
dis[nxt]=dis[cur]+w;
pre_lca(nxt,cur);
}
}
return ;
}
int lca(int x,int y)
{
if(dep[x]>dep[y])
swap(x,y);
for(int i=20;i>=0;i--)
if(dep[dp[y][i]]>=dep[x])
y=dp[y][i];
if(x==y)
return x;
for(int i=20;i>=0;i--)
if(dp[x][i]!=dp[y][i])
x=dp[x][i],y=dp[y][i];
return dp[x][0];
}
int main()
{
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
cin>>n>>m;
for(int i=1;i<n;i++)
{
int x,y,z;
cin>>x>>y>>z;
v[x].push_back((edge){y,z});
v[y].push_back((edge){x,z});
}
pre_lca(1,0);
while(m--)
{
int x,y;
cin>>x>>y;
cout<<dis[x]+dis[y]-2*dis[lca(x,y)]<<'\n';
}
return 0;
}
1557:祖孙询问
这个题就 3 3 3 种情况, x x x 是 x x x 和 y y y 的公共祖先,则 x x x 是 y y y 的祖先,反之亦然,如果两种情况都不是,就是平辈。
#include<bits/stdc++.h>
using namespace std;
const int N=1e6+5;
int n,m,s,a[N],dp[N][30],dep[N],dis[N];
vector<int>v[N];
void pre_lca(int cur,int fa)
{
dp[cur][0]=fa;
dep[cur]=dep[fa]+1;
for(int i=1;(1<<i)<=dep[cur];i++)
dp[cur][i]=dp[dp[cur][i-1]][i-1];
for(int i=0;i<v[cur].size();i++)
{
int nxt=v[cur][i];
if(nxt!=fa)
pre_lca(nxt,cur);
}
return ;
}
int lca(int x,int y)
{
if(dep[x]>dep[y])
swap(x,y);
for(int i=22;i>=0;i--)
if(dep[dp[y][i]]>=dep[x])
y=dp[y][i];
if(x==y)
return x;
for(int i=22;i>=0;i--)
if(dp[x][i]!=dp[y][i])
x=dp[x][i],y=dp[y][i];
return dp[x][0];
}
int main()
{
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
cin>>n;
for(int i=1;i<=n;i++)
{
int x,y,z;
cin>>x>>y;
if(y==-1)
{
s=x;
continue;
}
v[x].push_back(y);
v[y].push_back(x);
}
pre_lca(s,0);
cin>>m;
while(m--)
{
int x,y,z;
cin>>x>>y;
z=lca(x,y);
if(x==z)
cout<<"1\n";
else if(y==z)
cout<<"2\n";
else
cout<<"0\n";
}
return 0;
}
P3398 仓鼠找 sugar
首先我们要知道,我们容易发现,如果相交,记 x = l c a ( a , b ) x=lca(a,b) x=lca(a,b), y = l c a ( c , d ) y=lca(c,d) y=lca(c,d),则必有 x x x 在 c c c~ d d d 路径上或 y y y 在 a a a~ b b b 路径上,再想想,如果 x x x 在 a a a~ b b b 上,则 x x x 到 a a a 的距离加上 x x x 到 b b b 的距离一定等于 a a a 到 b b b 的距离,否则 x x x 一定不在 a a a~ b b b上,另一个也一样。
#include<bits/stdc++.h>
using namespace std;
const int N=1e6+5;
int n,m,s,a[N],dp[N][30],dep[N];
vector<int>v[N];
void pre_lca(int cur,int fa)
{
dp[cur][0]=fa;
dep[cur]=dep[fa]+1;
for(int i=1;(1<<i)<=dep[cur];i++)
dp[cur][i]=dp[dp[cur][i-1]][i-1];
for(int i=0;i<v[cur].size();i++)
{
int nxt=v[cur][i];
if(nxt!=fa)
pre_lca(nxt,cur);
}
return ;
}
int lca(int x,int y)
{
if(dep[x]>dep[y])
swap(x,y);
for(int i=22;i>=0;i--)
if(dep[dp[y][i]]>=dep[x])
y=dp[y][i];
if(x==y)
return x;
for(int i=22;i>=0;i--)
if(dp[x][i]!=dp[y][i])
x=dp[x][i],y=dp[y][i];
return dp[x][0];
}
int dis(int x,int y)
{
return dep[x]+dep[y]-2*dep[lca(x,y)];
}
int main()
{
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
cin>>n>>m;
for(int i=1;i<n;i++)
{
int x,y,z;
cin>>x>>y;
v[x].push_back(y);
v[y].push_back(x);
}
pre_lca(1,0);
for(int i=1;i<=m;i++)
{
int a,b,c,d;
cin>>a>>b>>c>>d;
int p1=lca(a,b),p2=lca(c,d);
if(dis(c,p1)+dis(d,p1)==dis(c,d)||dis(a,p2)+dis(b,p2)==dis(a,b))
cout<<"Y\n";
else
cout<<"N\n";
}
return 0;
}
1989

被折叠的 条评论
为什么被折叠?



