【倍增法lca】点的距离(链式前向星)

本文介绍了一种使用倍增法求解树上两点间距离的问题解决方法,包括树的构建、深度遍历、最近公共祖先的求解及最终距离计算。通过链式前向星存储树结构,利用递归深度优先搜索确定节点深度和父节点,再通过预处理获得节点间的跳跃关系,从而高效求解任意两点的最近公共祖先。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目描述

给定一棵有n个结点的树,Q个询问,每次询问点x到点y亮点之间的距离

输入

第一行一个n,表示有n个节。

接下来有n-1行,每行2个整数x,y表示x,y之间有一条连边。

然后一个整数Q,表示有Q次询问,接下来Q行每行2个整数x,y表示询问x到y的距离。

输出

输出Q行,每行表示每个询问的结果

样例输入

6
1 2
1 3
2 4
2 5
3 6
2
2 6
5 6

样例输出

3
4

题解

倍增法是由几个步骤配合完成的:

首先我们对输入的数据进行建树,然后对树进行深度遍历得到每个节点的深度以及记录它们的父节点。

然后我们用一个特殊的数组st[i][j]表示节点i上跳2^j次后得到的节点,其中st[i][0]表示第i个节点的父节点,有一个公式是需要理解的,st[ i ][ j ]=st[ st[i][j-1] ][ j-1 ],意思就是第i个节点上跳2^j个节点等于i节点向上跳2^(j-1)的这个节点向上跳2^(j-1)次方(2^j=2^(j-1)+2^(j-1)),j的取值范围为[1,log2 n]。

接着就是求两个节点的最近公共祖先,首先我们要把两个节点调整到同一深度,具体做法是是使深度大的上跳;当到达同一深度后同时上跳,直到上跳得到的节点相同,那么这个节点就是它们的最近公共祖先,在程序中用get_lca()来实现。

这题首先求出两个节点的lca,然后x,y两点之间的最短路径就是x->xy的最近公共祖先->y,它们的距离就等于x的深度加上y的深度减去两倍的公共祖先的深度。

 

关于链式前向星,它是通过保存边来保存一颗树的,首先用num_edge记录边的编号(顺序无关)。然后有一个first[i]数组,它保存的是与i点相连的已加入的最后一条边的num_edge编号(为什么这样保存后面说),然后开一个结构体Edge,next表示下一条边的num_edge编号,to表示这条边到达的点。

存储的方法是:每加入一条边(i,j),就把这条边加到i、j链表的首部,并更新first[i],first[j]数组。

广度优先遍历的方法就是,对于与i相连的所有点,找到最后一次加入的边的num_edge编号,i是边的起点(不是真正意义的起点),edge[i].to是边的终点,通过访问edge[i].next找到下一条与i相连的边的编号继续访问。

而深度优先遍历则是找到某条边的终点(不是真正意义的终点),将这个终点作为起点,继续访问它的终点循环下去。

#include<iostream>
#include<cstring>
using namespace std;
const int maxn=4e5+10;
int num_edge=0,father[maxn],dep[maxn],st[maxn][25];
int first[maxn];//x点最后一条边的编号
struct Edge
{
	int next;//下一条边的编号
	int to;//这条边到达的点
	//int dist;
}edge[maxn*2];
void add_edge(int from,int to)
{
	edge[++num_edge].next=first[from];
	edge[num_edge].to=to;
	first[from]=num_edge;//将这一条边作为最后一条边,下一条边其实是上一次存储的边
}
void dfs(int x,int fa)
{
	father[x]=fa;
	for(int i=first[x];i!=-1;i=edge[i].next)
	{
		int to=edge[i].to;
		if(to==fa)
			continue;
		dep[to]=dep[x]+1;
		dfs(to,x);
	}
}
int get_lca(int x,int y)
{
	if(dep[x]<dep[y])
		swap(x,y);
	for(int i=20;i>=0;i--)
		if(dep[ st[x][i] ]>=dep[y])
			x=st[x][i];
	if(x==y)
		return x;
	for(int i=20;i>=0;i--)
	{
		if(st[x][i]!=st[y][i])
		{
			x=st[x][i];
			y=st[y][i];
		}
	}
	return father[x];
}
int main()
{
	int n,q;
	scanf("%d",&n);
	memset(first,-1,sizeof(first));
	for(int i=1;i<n;i++)
	{
		int x,y;
		scanf("%d %d",&x,&y);
		add_edge(x,y);//链式前向星
		add_edge(y,x);
	}
	dep[1]=1;//选作为根节点
	dfs(1,0);
	for(int i=1;i<=n;i++)
		st[i][0]=father[i];//向上跳一格就是自己的父节点
	for(int j=1;j<=20;j++)
		for(int i=1;i<=n;i++)
			st[i][j]=st[ st[i][j-1] ][ j-1 ];

	scanf("%d",&q);
	while(q--)
	{
		int x,y;
		scanf("%d %d",&x,&y);
		int temp=get_lca(x,y);
		printf("%d\n",dep[x]+dep[y]-2*dep[temp]);
	}
	return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值