最近公共祖先

最近公共祖先( 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][j1]][j1]
初始状态:
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]2dis[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]2dep[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;
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值