基础知识-倍增求LCA

P3379 【模板】最近公共祖先(LCA)

题目描述
如题,给定一棵有根多叉树,请求出指定两个点直接最近的公共祖先。
输入格式
第一行包含三个正整数 N,M,S,分别表示树的结点个数、询问的个数和树根结点的序号。
接下来 N−1 行每行包含两个正整数 x, y,表示 x 结点和 y 结点之间有一条直接连接的边(数据保证可以构成树)。
接下来 M 行每行包含两个正整数 a,b,表示询问 a 结点和 b 结点的最近公共祖先。
输出格式
输出包含 M 行,每行包含一个正整数,依次为每一个询问的结果。
输入输出样例
输入
5 5 4
3 1
2 4
5 1
1 4
2 4
3 2
3 5
1 2
4 5
输出
4
4
1
4
4
说明/提示
对于 30% 的数据,10<=N≤10,10<=M≤10。
对于 70% 的数据,10000<=N≤10000,10000<=M≤10000。
对于 100% 的数据,500000<=N≤500000, 500000<=M≤500000。

例题链接
学习资料


在暴力法找LCA中,我们每次向上跳一格,显然速度太慢,所以我们用倍增的方法,来尽量减少向上跳得次数;
假设求x,y的LCA,其具体做法如下:

先求出倍增表:(因为二进制可以表示十进制下的任何数,所以一定可以跳到你想跳到的点。)
因为22 = 21 * 21,说明你要跳22步,可以先跳21步再跳21步,即2 * 21,故你要跳到u号结点的2j的祖先,可以先跳2j-1再从u的2j-1的祖先处跳2j-1,即2 * 2j-1

for(int i=1;(1<<i)<=dep[u];i++) f[u][i] = f[f[u][i-1]][i-1];


先让结点x,y的深度相同,我们让深度深的结点尽可能的向上跳,直到dep[x] = dep[y],跳到深度相同时(可能一开始深度就相同),我们判断一下,此时x,y是否重合(树可能是一条链),如果重合直接返回;

	if(dep[x] < dep[y]) swap(x,y);
	for(int i=20;i>=0;i--) {
		if(dep[f[x][i]] >= dep[y]) x = f[x][i];
		if(x==y) return x;
	}


我们让x,y结点尽可能同时往上跳,但是跳到的结点不能重合,因为那样会导致跳到的结点是公共祖先,但可能不是最近的。最终两节点会跳到其最近公共祖先的儿子节点,此时返回x或y的父亲结点即可

	for(int i=20;i>=0;i--) if(f[x][i] != f[y][i]) x = f[x][i],y = f[y][i];
	return f[x][0];

Code:

#include<bits/stdc++.h>
#define bug(x) cout<<#x<<" = "<<x<<endl;
using namespace std;
typedef long long ll;
const int N = 5e5 + 10;
const int inf = 0x3f3f3f3f;
int cnt;
int n,m,s;
int dep[N],head[N],f[N][30];
struct Node {
	int to,next;
}e[N<<2]; 
void add_edge(int u,int v) {
	cnt++;
	e[cnt].to = v;
	e[cnt].next = head[u];
	head[u] = cnt;
}
void dfs(int u,int fa) {
	dep[u] = dep[fa] + 1;
	for(int i=1;(1<<i)<=dep[u];i++) f[u][i] = f[f[u][i-1]][i-1];
	
	for(int i=head[u];i;i=e[i].next) {
		int to = e[i].to;
		if(to == fa) continue;
		f[to][0] = u;
		dfs(to,u);
	}
}
int LCA(int x,int y) {
	if(dep[x] < dep[y]) swap(x,y);
	for(int i=20;i>=0;i--) {
		if(dep[f[x][i]] >= dep[y]) x = f[x][i];
		if(x==y) return x;
	}
	for(int i=20;i>=0;i--) if(f[x][i] != f[y][i]) x = f[x][i],y = f[y][i];
	return f[x][0];
	
}
int main() {
	cin>>n>>m>>s;
	for(int i=1;i<n;i++) {
		int u,v;
		scanf("%d%d",&u,&v);
		add_edge(u,v);
		add_edge(v,u);
	}
	dfs(s,0);
	while(m--) {
		int x,y;
		scanf("%d%d",&x,&y);
		printf("%d\n",LCA(x,y));
	}
	return 0;
}
/*

*/
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值