LCA学习笔记

LCA一共学习了三种方法,分别是
1.倍增
2.RMQ
3.树链剖分

首先是倍增,先是预处理,倍增需要处理出f[i][x] 表示x点向上走2的i次方所到的点,先处理所有f[0][x],f[0][x] = father[x],因为x点向上走2的0次方(也就是1)就是它的父亲,然后再推到f[i][x] = (f[i - 1][f[i - 1][x]])表示先从x点向上走2的i - 1次方,然后再向上走2的i - 1次方,预处理完毕。
再是求LCA,先让深度较大的那个点转换成和深度较浅的点的深度相同,然后就会有两种情况
1.如果两个点发现在同一个点上,就代表其中一个点就是他们的公共祖先了,直接输出答案就好
2.如果两个点不在一个点上,我们采用逐步逼近法,因为LCA上面的点肯定都是相同的,从大到小枚举2的幂,如果不相同说明还在LCA的下面,就继续向上走,一直到最后绝对只差1。
两个情况特判一下就好。

预处理代码

int k = 17;
for(int i = 1;i <= n;i ++)
	f[0][i] = father[i];
for(int i = 1;i <= k;i ++)
{
	for(int j = 1;j <= n;j ++)
		f[i][j] = f[i - 1][f[i - 1][j]];
}

靠近深度代码

int k = 17;
void get(int x,int y)
{
	if(d[x] < d[y]) // 保证x 的深度大于 y 
		swap(x,y);
	for(int i = k;i >= 1;i --)
		if(d[f[i][x]] >= d[y])
			x = f[i][x];
}

寻找LCA代码

int k = 17;
void get(int x,int y)
{
	for(int i = k;i >= 1;i --)
		if(f[i][x] != f[i][y])
		{
			x = f[i][x];
			y = f[i][y];
		}
	
}

然后是RMQ求LCA
我们先认识一下原理,先跑一遍DFS,记录第一次DFS到的点的位置和深度,然后每次查询,跑一下RMQ,求出区间最浅,就是公共祖先
详见代码

#include <cstdio>
#include <algorithm>
#include <cstring>
using namespace std;
const int M = 500000;
struct Edge{
	int fr;
	int to;
	int next;
}edge[M * 2 + 5];
struct finkery{
	int d;
	int b;
};
int head[M * 2 + 5];
int mark[M * 2 + 5];
int num;
int num_1;
int pos[M * 2+ 5];
finkery q[M * 2 + 5];
int mark_1[M * 2 + 5];
finkery dp[20][M * 2 + 5];
int log1[M * 2+ 5];
finkery min(finkery x,finkery y)
{
	if(x.d < y.d)
		return x;
	return y;
}
void add_edge(int x,int y)
{
	edge[++ num].fr = x;
	edge[num].to = y;
	edge[num].next = head[x];
	head[x] = num;
}
void dfs(int x,int y)
{
	if(! mark[x])
	{
		pos[x] = ++ num_1;
		q[num_1].b = x;
		q[num_1].d = y;
		mark[x] = 1;
	}
	for(int i = head[x];i;i = edge[i].next)
	{
		if(mark_1[i] || mark[edge[i].to]) continue;
		mark_1[i] = 1;
		dfs(edge[i].to,y + 1);
		q[++ num_1].b = x;
		q[num_1].d = y;
	}
}
int main()
{
	int n,m,s;
	scanf("%d%d%d",&n,&m,&s);
	for(int i = 1;i <= n - 1;i ++)
	{
		int fr,to;
		scanf("%d%d",&fr,&to);
		add_edge(fr,to);
		add_edge(to,fr);
	}
	dfs(s,1);
	/*for(int i = 1;i <= num_1;i ++)
		printf("%d ",q[i].b);
	for(int i = 1;i <= n;i ++)
		printf("%d ",pos[i]);*/
	for(int i = 1;i <= num_1;i ++)
		dp[0][i].d = q[i].d,dp[0][i].b = q[i].b;
/*	for(int i = 1;i <= num_1;i ++)
		printf("%d ",q[i].d);*/
	for(int k = 1;(1 << k) <= num_1;k ++)
		for(int i = 1;i <= num_1;i ++)
			if(i + (1 << (k - 1)) >= num_1)
				dp[k][i] = dp[k - 1][i];
			else
				dp[k][i] = min(dp[k - 1][i],dp[k - 1][i + (1 << (k - 1))]);
/*	printf("\n");
	for(int i = 1;i <= num_1;i ++)
		printf("%d ",q[i].b);
	printf("\n");
	for(int k = 0;(1 << k) <= num_1;k ++)
	{
		for(int i = 1;i <= num_1;i ++)
			printf("%d ",dp[k][i].d);
		printf("\n");
		for(int i = 1;i <= num_1;i ++)
			printf("%d ",dp[k][i].b);
		printf("\n");
	}*/
	for(int i = 2;i <= num_1;i ++)
		log1[i] = log1[i >> 1] + 1;
	for(int i = 1;i <= m;i ++)
	{
		int l,r;
		scanf("%d%d",&l,&r);
		r = pos[r];
		l = pos[l];
		if(r < l)
			swap(r,l);
	//	printf("%d %d\n",l,r);
		int mid = log1[r - l + 1];
	//	printf("%d\n",mid);
		//printf("%d ",min(dp[mid][l].d,dp[mid][r - (1 << mid) + 1].d));
		if(dp[mid][l].d < dp[mid][r - (1 << mid) + 1].d)
			printf("%d\n",dp[mid][l].b);
		else
			printf("%d\n",dp[mid][r - (1 << mid) + 1].b);
		//printf("\n");
	}
	return 0;
}

第三种是树链剖分,我们可以设一个点有一个重儿子和若干个轻儿子,重儿子就是含有点较多的儿子,用两次DFS,第一次维护深度,父亲,子树数量,第二题维护top,每个点标记上父亲和深度,和自己所在重链的top,如果两个点的top是同一个,就输出深度较浅的那个,否则top较低的那个跳到top的父亲
详见代码

// luogu-judger-enable-o2
#include <cstdio>
#include <algorithm>
#include <cstring>
using namespace std;
const int M = 500000;
struct Edge{
	int fr;
	int to;
	int next;
}edge[M * 2 + 5];
int head[M * 2 + 5];
int f[M + 5];
int size[M + 5];
int top[M + 5];
int son[M + 5];
int mark[M * 2 + 5];
int d[M + 5];
int mark_1[M];
int num;
void add_edge(int x,int y)
{
	num ++;
	edge[num].fr = x;
	edge[num].to = y;
	edge[num].next = head[x];
	head[x] = num;
}
void dfs(int x,int dnow)
{
	size[x] = 1;
	d[x] = dnow;
	int maxn = 0;
	for(int i = head[x];i;i = edge[i].next)
	{
		if(mark[i] || mark_1[edge[i].to]) continue;
		mark[i] = 1;
		mark_1[edge[i].to] = 1;
		dfs(edge[i].to,dnow + 1);
		f[edge[i].to] = x;
		if(size[edge[i].to] > maxn)
		{
			maxn = size[edge[i].to];
			son[x] = edge[i].to;
		}
		size[x] += size[edge[i].to];
		mark[i] = 0;
		mark_1[edge[i].to] = 0;
	}
}
void dfs2(int x,int top_1)
{
	top[x] = top_1;
	for(int i = head[x];i;i = edge[i].next)
	{
		if(mark[i] || mark_1[edge[i].to])
			continue;
		mark[i] = 1;
		mark_1[edge[i].to] = 1;
		if(edge[i].to == son[x]) dfs2(edge[i].to,top_1);
		else dfs2(edge[i].to,edge[i].to);
		mark[i] = 0;
		mark_1[edge[i].to] = 0;
	}
}
void print(int l,int r)
{
	while(top[l] != top[r])
	{
		if(d[top[l]] < d[top[r]])
			r = f[top[r]];
		else
			l = f[top[l]];
	}
	printf("%d\n",d[l] < d[r] ? l : r);
	return;
}
int main()
{
	int n,m,s;
	scanf("%d%d%d",&n,&m,&s);
	for(int i = 1;i <= n - 1;i ++)
	{
		int fr;
		int to;
		scanf("%d%d",&fr,&to);
		add_edge(fr,to);
		add_edge(to,fr);
	}
	mark_1[s] = 1;
	dfs(s,0);
	mark_1[s] = 1;
	dfs2(s,s);
	/*for(int i = 1;i <= n;i ++)
		printf("%d ",f[i]);*/
	for(int i = 1;i <= m;i ++)
	{
		int l,r;
		scanf("%d%d",&l,&r);
		if(top[l] == top[r])
			printf("%d\n",d[l] < d[r] ? l : r);
		else
			print(l,r);
	}
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值