LCA最近公共祖先求解(四种方法模板)

详细见:最近公共祖先 - OI Wiki

目录

定义:

​性质:

题目:【模板】最近公共祖先(LCA) 

朴素算法:

 树链剖分模板:

倍增模板:

tarjan离线求解模板:

ST表+RMQ在线求解模板:


定义:

性质:

题目:【模板】最近公共祖先(LCA) 

题目链接:【模板】最近公共祖先(LCA) - 洛谷

题面:

  

朴素算法:

 树链剖分模板:

#include<bits/stdc++.h>
using namespace std;
#define endl "\n"
#define N 500005
int head[N], to[N << 1], nex[N << 1], cnt = 1;
int sz[N], dep[N], fa[N], son[N], top[N];
void init(){
	cnt = 1;
	memset(sz, 0, sizeof(sz));
	memset(dep, 0, sizeof(dep));
	memset(fa, 0, sizeof(fa));
	memset(son, 0, sizeof(son));
	memset(top, 0, sizeof(top));
	memset(head, 0, sizeof(head));
}
void add(int x, int y){
	to[cnt] = y;
	nex[cnt] = head[x];
	head[x] = cnt++;
}
void dfs1(int rt, int f){
	dep[rt] = dep[f] + 1;
	sz[rt] = 1;
	fa[rt] = f;
	for(int i = head[rt]; i; i = nex[i]){
		if(to[i] == f){
			continue;
		}
		dfs1(to[i], rt);
		if(!son[rt] || sz[to[i]] > sz[son[rt]]){
			son[rt] = to[i];
		}
		sz[rt] += sz[to[i]];
	}
}
void dfs2(int rt, int t){
	top[rt] = t;
	if(!son[rt]) return ;
	dfs2(son[rt], t);
	for(int i = head[rt]; i; i = nex[i]){
		if(to[i] == fa[rt] || to[i] == son[rt]){
			continue;
		}
		dfs2(to[i], to[i]);
	}
}
int solve(int x, int y){
	while(top[x] != top[y]){
		if(dep[top[x]] < dep[top[y]]){
			swap(x, y);
		}
		x = fa[top[x]];
	}
	return dep[x] < dep[y] ? x : y;
}
int main(){
	ios::sync_with_stdio(false);
	int n, m, s;
	while(cin >> n >> m >> s){
		init();
		int x, y;
		for(int i = 0; i < n - 1; i++){
			cin >> x >> y;
			add(x, y);
			add(y, x);
		}
		dfs1(s, 0);
		dfs2(s, s);
		for(int i = 0; i < m; i++){
			cin >> x >> y;
			cout << solve(x, y) << endl;
		}
	}
	return 0;
}

倍增模板:

#include<bits/stdc++.h>
using namespace std;
#define N 500005
#define endl "\n"
int head[N], to[N<<1], nex[N<<1], cnt=1;
int fa[N][21], dep[N];
void add(int x, int y){
	to[cnt] = y;
	nex[cnt] = head[x];
	head[x] = cnt++;
}
void dfs(int rt, int f){
	dep[rt] = dep[f] + 1;
	fa[rt][0] = f;
	for(int i = 1; (1 << i) <= dep[rt]; i++){ //进制由小到大递推 
		fa[rt][i] = fa[fa[rt][i - 1]][i - 1];
	}
	for(int i = head[rt]; i; i = nex[i]){
		if(to[i] == f){
			continue;
		}
		dfs(to[i], rt);
	}
}
int LCA(int x, int y){
	if(dep[x] < dep[y]){
		swap(x, y);
	}
	for(int i = 20; i >= 0; i--){ //进制由大到小开始组合 
		if(dep[fa[x][i]] >= dep[y]){
			x = fa[x][i];
		}
	} 
	if(x == y){ //注意特判 
		return x;
	}
	for(int i = 20; i >= 0; i--){ //进制从小到大开始组合 
		if(fa[x][i] != fa[y][i]){
			x = fa[x][i];
			y = fa[y][i];
		}
	}
	return fa[x][0]; //这一步尤其考虑,为什么x,y不是LCA,二期父节点一定是LCA 
}
int main(){
	ios::sync_with_stdio(false);
	int n, m, s;
	while(cin >> n >> m >> s){
		memset(head, 0, sizeof(head));
		cnt = 1;
		int x, y;
		for(int i = 0; i < n - 1; i++){
			cin >> x >> y;
			add(x, y);
			add(y, x);
		}
		dfs(s, 0);
		for(int i = 0; i < m; i++){
			cin >> x >> y;
			cout << LCA(x, y) << endl;
		}
	}
	return 0;
}

tarjan离线求解模板:

 思想:

本质就是利用dfs的节点顺序,让我们正在递归两个节点的最近公共祖先时,显然这两个点是属于其子树的节点,那么我们第一次遍历完两个需要求解的两个点时,其最近的尚未被完全遍历完子节点的节点就是他们两个的最近公共祖先

#include<bits/stdc++.h>
using namespace std;
#define endl "\n"
#define N 500005
int head[N], to[N<<1], nex[N<<1], cnt ;
int visit[N], fa[N];
int qhead[N<<1], qnex[N<<1], qcnt = 1, qid[N<<1], ans[N], qto[N<<1];
void add_edge(int x, int y){
	to[cnt] = y;
	nex[cnt] = head[x];
	head[x] = cnt++;
}
void add_query(int x, int y, int w){
	qto[qcnt] = y;
	qnex[qcnt] = qhead[x];
	qid[qcnt] = w;
	qhead[x] = qcnt++;
}
int findx(int x){
	if(fa[x] == x){
		return x;
	}
	return fa[x] = findx(fa[x]);
}
void tarjan(int rt, int f){
	for(int i = head[rt]; i; i = nex[i]){
		if(to[i] == f)continue;
		tarjan(to[i], rt);
		fa[to[i]] = rt;
	}
	visit[rt] = 1;
	for(int i = qhead[rt]; i; i = qnex[i]){
		if(visit[qto[i]] == 0)continue; 
		ans[qid[i]] = findx(qto[i]);
	}
	
}
int main(){
	ios::sync_with_stdio(false);
	int n, m, s;
	while(cin >> n >> m >> s){
		int x, y;
		memset(head, 0, sizeof(head));
		cnt = 1, qcnt = 1;
		memset(qhead, 0, sizeof(qhead));
		for(int i = 1; i < n; i++){
			cin >> x >> y;
			add_edge(x, y);
			add_edge(y, x);
			fa[i] = i;
		}
		fa[n] = n;
		for(int i = 1; i <= m; i++){
			cin >> x >> y;
			add_query(x, y, i);
			add_query(y, x, i);
		}
		tarjan(s, 0);
		for(int i = 1; i <= m; i++){
			cout << ans[i] << endl;
		}
	}
	return 0;
}

ST表+RMQ在线求解模板:

思想:

利用dfs的遍历,在遍历两个点的时候,一定会在中间返回到其最近公共祖先,这个时候的公共祖先也就是这两个点的最小值

#include<bits/stdc++.h>
using namespace std;
#define N 500005
#define endl "\n"
int head[N], to[N<<1], nex[N<<1],cnt = 1;
int id[N], tot, last;
int st[N<<2][30];
void add(int x, int y){
	to[cnt] = y;
	nex[cnt] = head[x];
	head[x] = cnt++;
}
void dfs(int rt, int f){
	id[rt] = last = ++tot;
	st[tot][0] = rt;
	for(int i = head[rt]; i; i = nex[i]){
		if(to[i] == f)continue;
		dfs(to[i], rt);
		st[++tot][0] = rt;
	}
}
int Min(int a, int b){
	return id[a] < id[b] ? a : b;
}
int main(){
	ios::sync_with_stdio(false);
	int n, m, s;
	while(cin >> n >> m >> s){
		int x, y;
		cnt = 1;
		tot = 0;
		last = 0;
		memset(head, 0, sizeof(head));
		for(int i = 1; i < n; i++){
			cin >> x >> y;
			add(x, y);
			add(y, x);
		}
		dfs(s, 0);
		int k = log(last) / log(2);
		for(int j = 1; j <= k; j++){
			for(int i = 1; i + (1 << j) - 1 <= last; i++){
				st[i][j] = Min(st[i][j - 1], st[i + (1 << (j - 1))][j - 1]);
			}
		}
		for(int i = 1; i <= m; i++){
			cin >> x >> y;
			x = id[x];
			y = id[y];
			if(x > y){
				swap(x, y);
				
			}int k = log(y - x + 1) / log(2);
				cout << Min(st[x][k], st[y - (1 << k) + 1][k]) << endl;
		}
	}
	return 0;
}

  • 4
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值