倍增求LCA

一:定义

LCA指的是最近公共祖先。具体地,给定一棵有根数,若结点z既是结点x的祖先,也是结点y的祖先,则称z是x,y的公共祖先。在x,y的公共祖先中,深度最大的那个节点成为x,y的最近公共祖先,记为LCA(x,y)。

我们举个例子,如图4-4-1所示LCA(4,5)=2,LCA(5,6)=1,LCA(2,3)=1;

请添加图片描述

二:如何求LCA

我么考虑“暴力”要怎么实现找两点的LCA。
请添加图片描述
e.g. LCA(7,5)=2;

先DFS一遍找出每个点的DEP(深度)。然后先从深度大的7往上跳,跳到和5深度相同的点4,发现还是没有到同一个点,那么4、5继续往上跳,直到跳到2位置,发现点一样了,那么2就是它们的LCA了。

三:如何优化这个方法

我们考虑这个方法慢在哪里,当然是对于每个点,一次往上跳一步,导致了效率低,那么如何优化呢?只要一次能向上跳多步,效率自然就高了。

树上倍增法

树上倍增法是一个很重要的算法。设f[x,k]表示x的2^k辈祖先,即从x向根结点走
2^k步到达的结点,特别地,若该结点不存在,则令f[x,k]=0。f[x,0]就是x的父结点。 因为x向根结点走2^k <=> 向根走2^(k-1)步, 再走2^(k-1)步。
所以对于k∈[1,logn],有f[x][k]=f[f[x][k-1]][k-1]。

这类似于一个动态规划的过程,“阶段”就是结点的深度,因此,我们可以对树进行遍历,由此得到f[x.0],再计算f数组所有值。
以上部分是预处理,时间复杂度为O(nlogn)。之后可以多次对不同的x,y计算LCA,每次询问的时间复杂度为O(logn)。

基于f数组计算LCA(x,y)分为以下几步:
①设dep[x]表示x的深度。不妨设dep[x]>=dep[y](否则,可交换x,y)。
②用二进制拆分思想,把x向上调整到与y同一深度。
具体来说,就是依次尝试从x向上走k=2^(logn)...2^12^0步,若到达的结点比y深,则令x=f[x,k]。
③若此时x=y,说明已经找到了LCA,LCA就等于y。
④若此时x!=y,那么x,y继续往上跳,用二进制拆分思想,把x,y同时向上调整,并保持深度一致且二者不相会。
具体来说,就是依次尝试把x,y同时向上走k=2^(logn)2^12^0步,若f[x,k]!=f[y,k].(即仍未相会),则令x=f[x,k],y=f[y,k]。
⑤此时x,y必定只差一步就相会了,它们的父结点f[x,0]就是LCA。

【代码实现】
预处理:

void Deal_first(int u,int father){
	Dep[u] = Dep[father] + 1;
	for(int i=0;i<=19;i++)
		f[u][i+1] = f[f[u][i]][i];
	for(int e=first[u];e;e=next[e]){
		int v=go[e];
		if(v == father)
			continue;
		f[v][0] = u;
		//v向上跳2^0 = 1就是u
		Deal_first(v,u);
	}
}

查询x,y的LCA:

int LCA(int x,int y){
	if(Dep[x]<Dep[y])
		swap(x,y);//让x深度较大
//我们用“暴力”的思想:先将x、y跳到一个深度,然后一起往上跳
	for(int i=20;i>=0;i--){//一定要倒着for
		if(Dep[f[x][i]]>=Dep[y])
			x=f[x][c][i];//先跳到同一层
		if(x==y) 
			return x;
	}
	for(int i=20;i>=0;i--){//此时x,y已跳到同一层
		if(f[x][i]!=f[y][i])//如果f[x][i]和f[y][i]不同才跳
		{
			x=f[x][i];
			y=f[y][i];
		}
	}
	return f[x][0];//x,y是深度最浅且不同的点,即LCA的子结点
}

例题:

点的距离 LibreOJ - 10130

题目描述
给定一棵 n 个结点的树,Q个询问,每次询问点 x 到点 y 两点之间的距离。

输入格式
第一行一个正整数 n,表示这棵树有 n 个节点;
接下来 n-1 行,每行两个整数 x,y 表示 x,y 之间有一条连边;
然后一个整数 Q,表示有 Q 个询问;
接下来 Q 行每行两个整数 x,y 表示询问 x 到 y 的距离。

输出格式
输出 Q 行,每行表示每个询问的答案。

样例
Input
6
1 2
1 3
2 4
2 5
3 6
2
2 6
5 6
Output
3
4
数据范围与提示
对于全部数据,1≤n≤10^5,1≤x,y≤n。

分析:我们需要构建一棵树,然后距离就是x的深度+y的深度-LCA(x,y)的深度。
AC代码:

#include <bits/stdc++.h>
using namespace std;
const int ONE=100010;
int n,Q,x,y;
int nexx[ONE*2],first[ONE*2],go[ONE*2],tot;
int Dep[ONE];
int f[ONE][22];
void Add(int u,int v){
	nexx[++tot]=first[u];
	first[u]=tot;
	go[tot]=v;
}
void Deal_first(int u,int father){
	Dep[u]=Dep[father]+1;
	for(int i=0;i<=19;i++){
		f[u][i+1]=f[f[u][i]][i];
	}
	for(int e=first[u];e;e=nexx[e]){
		int v=go[e];
		if(v==father)
			continue;
		f[v][0]=u;
		Deal_first(v,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 dist(int x,int y){
	return Dep[x]+Dep[y]-2*Dep[LCA(x,y)];
}
int main(){
	tot=0;
	scanf("%d",&n);
	for(int i=1;i<n;i++){
		scanf("%d%d",&x,&y);
		Add(x,y);
		Add(y,x);
	}
	Deal_first(1,0);
	scanf("%d",&Q);
	while(Q--){
		scanf("%d%d",&x,&y);
		printf("%d\n",dist(x,y));
	}
	return 0;
}

详细请看一本通

  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Tarjan算法是一种用于解决最近公共祖先(LCA)问题的离线算法。离线算法指的是在读取所有查询之后一次性计算所有查询的答案,而不是每读取一个查询就计算一次。\[1\] 在Tarjan算法中,需要使用并查集来实现。并查集是一种数据结构,用于维护元素之间的集合关系。下面是一个并查集的模板代码: ```cpp int fa\[100000\]; void reset(){ for (int i=1;i<=100000;i++){ fa\[i\]=i; } } int getfa(int x){ return fa\[x\]==x?x:getfa(fa\[x\]); } void merge(int x,int y){ fa\[getfa(y)\]=getfa(x); } ``` 在Tarjan算法的伪代码中,首先标记当前节点为已访问状态。然后遍历当前节点的子节点,递归调用Tarjan函数并合并子节点。接下来,遍历与当前节点有查询关系的节点,如果该节点已经访问过,则输出当前节点和该节点的LCA(通过并查集的查找函数getfa获取)。\[3\] 以上是关于Tarjan算法解LCA的相关内容。 #### 引用[.reference_title] - *1* [Tarjan 算法解决 LCA 问题](https://blog.csdn.net/chengqiuming/article/details/126878817)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* *3* [详解使用 Tarjan LCA 问题(图解)](https://blog.csdn.net/weixin_34315485/article/details/93801193)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值