LCA

LCA算法详解

总结lca

Lca的主要功能是找最近公共祖先。提到祖先,最容易想起的是x叉树。

首先,要给每一个结点都编号,按照先序遍历。

 

因此,可以开一个数组,记录发现时间found,time++但是要注意,在编程时不能用time这个变量,与某个函数冲突。

 

    当我们找3和4的公共祖先时,我们会发现,当找到1那里时,会发现,found[1]一定小于found[3],那么说明找过头了。那如果找到4那里的话,又会发现found[4]大于found[3]。

    这么一来,和我们以前学过的二分挺像的,先跳上去step=(lo+hi)/2 ; mid=up(b,step),由b结点跳这么多步。如果跳过头了,那么hi=mid,反之lo=mid。

    但是,真的是这样吗?当我们要跳step步时,并不一定是我们已经算出来的1、2、4,所以,要执行up(b,step)是非常复杂的。时间复杂度是logn*logn。

梁老师说,这是他们以前的写法,后来一次比赛,看到第一名的世界冠军用了另外一种不同的写法,他们感到非常不解,后来经过一番争论,确定了这种方法的准确性,就是我们学的。

 

    先深搜得出f[now][i]就是由当前的点,向上跳2^i步可以到达的地方。一开始f[now][0]为father。

    如何计算呢,假设要往上跳2^x的点,在dfs时,就利用倍增的思想,先跳到2^x-1的点后,从那个点再往上跳2^x-1个点。因为深搜由上往下,所以前面的点一定都已经算好了,所以能够求得出来。

    如图所示,求A和B的最近公共祖先。我们还是像以前一样往上跳。假设先跳了64步,结果发现跳过头了。如果是上一种方法,会通过up函数跳上去。但是这种方法选择不跳。

    于是,我们再看32步,跳,发现没有过头。跳过去以后在x点。那么应该再跳多少步呢?32步?不行,32步一条不就又跳到之前过头的地方了吗?所以我们跳的步数一定要小于32步,最大是31步。

    我们会发现1+2+4+8+16=31。所以说,无论是多少步,都可以到达。因此这种方法,不用往回跳,只要往前跳就可以了。条件是found[x]>found[A],我们的目标是,要跳到c点的后一个点,最后再输出c的祖先就可以了。所以不可以found[x]==found[A]。

    我尝试了一下可不可以达到c点然后直接输出now,事实证明好像不行。

牧场行走

题目描述

N头牛(2<=n<=1000)别人被标记为1到n,在同样被标记1到n的n块土地上吃草,第i头牛在第i块牧场吃草。 这n块土地被n-1条边连接。 奶牛可以在边上行走,第i条边连接第Ai,Bi块牧场,第i条边的长度是Li(1<=Li<=10000)。 这些边被安排成任意两头奶牛都可以通过这些边到达的情况,所以说这是一棵树。 这些奶牛是非常喜欢交际的,经常会去互相访问,他们想让你去帮助他们计算Q(1<=q<=1000)对奶牛之间的距离。

输入格式

*第一行:两个被空格隔开的整数:N和Q

*第二行到第n行:第i+1行有两个被空格隔开的整数:AI,BI,LI

*第n+1行到n+Q行:每一行有两个空格隔开的整数:P1,P2,表示两头奶牛的编号。

输出格式 1649.out

*第1行到第Q行:每行输出一个数,表示那两头奶牛之间的距离。

输入样例

4 2
2 1 2
4 3 2
1 4 3
1 2
3 2

输出样例

2
7

       这题需要算出两头奶牛之间的距离,利用我们学的求出最近公共祖先以后,其实他们俩的距离就只要算出A和B离祖先的距离相加就可以了。

#include<iostream>
#include<cstdio>
#include<cmath>
using namespace std;
int xx,yy,ll,cur=-1,n,q,v[1005],aa,bb;
int head[1005],dis[1005],times,found[1005],f[1005][15];
struct cow
{
	int ta,next,l;
}e[2005];
void add(int x,int y,int ll)
{
	cur++;
	e[cur].ta=y;
	e[cur].next=head[x];
	e[cur].l=ll;
	head[x]=cur;
}
void dfs(int now,int father)
{
	v[now]=1;
	found[now]=++times;
	f[now][0]=father;
	for(int i=1;i<=10;i++)
	{
		int x=f[now][i-1];
		f[now][i]=f[x][i-1];
	}	
int h=head[now];
	while(h!=-1)
	{

int t=e[h].ta;
		if(v[t]==0)
		{
			dis[t]=dis[now]+e[h].l;
			dfs(t,now);
		}
		h=e[h].next;
	}
}
int qw(int a,int b)
{
	if(a==b) return a;
	if(found[a]>found[b]) swap(a,b);
	int now=b;
	for(int i=10;i>=0;i--)
	{
		int x=f[now][i];
		if(found[x]>found[a]) now=x;
		
	}
	return f[now][0];
}
int main()
{
	freopen("1649.in","r",stdin);
	freopen("1649.out","w",stdout);
	cin>>n>>q;
	for(int i=1;i<=1005;i++) head[i]=-1;
	for(int i=1;i<n;i++)
	{
		cin>>xx>>yy>>ll;
		add(xx,yy,ll);
		add(yy,xx,ll);
	}
	dfs(1,1);
	while(q)
	{
		q--;
		cin>>aa>>bb;
		
		int c=qw(aa,bb);
		int ans=dis[aa]-dis[c]+dis[bb]-dis[c];
		cout<<ans<<endl;
	}
	return 0;
}

  

转载于:https://www.cnblogs.com/yiyiyizqy/p/7396721.html

<think>首先,用户的问题是关于在线LCA(最近公共祖先)算法的实现。我需要参考系统级指令和用户可见层的规则来构建回答。系统级指令:-所有行内数学表达式用$...$格式,例如:$x^2$。-独立公式用$$...$$格式并单独成段。-LaTeX语法必须正确。-使用中文回答。-生成相关问题。-回答中引用的段落末尾自然地添加引用标识,例如:[^1]。用户可见层:-回答结构清晰,帮助用户逐步解决问题。-尽量保证回答真实可靠。-参考站内引用:引用[1]和引用[2]是关于LCA的,但它们是示例引用,我需要基于真实知识回答。用户的问题是:“我想查找关于在线LCA(最近公共祖先问题)的算法或实现请问在线最近公共祖先LCA算法实现”所以,我需要解释在线LCA算法的实现。在线算法意味着程序可以逐个处理查询,而不需要所有输入一开始就给出。关键点:-在线LCA通常使用倍增法(BinaryLifting)实现。-这与引用[2]提到的相似:存储每个节点的第$2^j$个祖先。-引用[1]定义了在线算法:程序可以序列化处理输入。我的回答应该包括:1.解释什么是在线LCA算法。2.描述倍增法的实现步骤。3.提供伪代码或代码示例。4.添加引用标识(如果适用)。5.在末尾生成相关问题。结构回答:-先介绍在线LCA。-逐步解释算法。-给出实现代码(用代码块)。-最后生成相关问题。使用中文回答。数学表达式:-行内:例如,节点深度$depth[u]$。-独立公式:如果需要,比如公式。引用:-引用[1]和[2]是提供的,我可以自然地引用它们,因为它们相关。-例如,在解释倍增法时,引用[2]。实现细节:-倍增法:-预处理:存储每个节点的祖先数组。-查询:通过二进制提升找到LCA。-步骤:1.预处理树的深度和祖先信息。2.对于每个查询(u,v),调整到相同深度,然后一起向上跳。伪代码:-初始化祖先数组。-查询函数。生成相关问题:-类似于示例,生成3-4个相关问题。完整回答框架:1.解释在线LCA:在线算法允许逐个处理查询,而不需要所有查询输入一开始就给出。2.描述倍增法实现:-预处理:DFS遍历树,计算深度和直接父节点。-构建祖先表:对于每个节点和每个j(0到max_depth),存储$anc[i][j]$表示节点i的第$2^j$个祖先。-查询:将两个节点升到相同深度,然后逐步向上跳。3.提供代码示例(用Python或类似语言)。4.引用:[^1]用于在线算法定义,[^2]用于倍增法实现。5.生成相关问题。确保LaTeX正确:-行内:$depth[u]$,$2^j$-独立:如果有公式,如$$\text{LCA}(u,v)=\text{...}$$,但可能不需要。代码块:使用```language```格式。最终,生成相关问题:例如:1.在线LCA算法的复杂度分析?2.如何优化倍增法?3.在线LCA的应用场景?</think>### 在线最近公共祖先(LCA)算法实现 在线最近公共祖先(LCA)算法用于在有根树中快速回答查询,即给定两个节点$u$和$v$,找到它们最近的公共祖先。在线算法的核心特点是它可以逐个处理查询输入,而无需预先知道所有查询[^1]。这与离线算法(如Tarjan算法)不同,后者需要一次性加载所有查询。在线LCA的常用实现是**倍增法(Binary Lifting)**,它通过预处理树的祖先信息来高效支持查询。下面我将逐步解释算法原理并提供代码实现。 #### 算法原理 倍增法的核心思想是预处理每个节点的$2^j$级祖先(即节点向上跳$2^j$步的祖先),从而在查询时通过二进制提升快速调整节点深度并找到LCA。算法分为两个阶段: 1. **预处理阶段**: - 通过DFS遍历树,计算每个节点的深度$depth[u]$和直接父节点(即$2^0$级祖先)。 - 构建一个祖先表$anc$,其中$anc[u][j]$存储节点$u$的第$2^j$个祖先。利用动态规划:$anc[u][j] = anc[ anc[u][j-1] ][j-1]$(当$j \geq 1$时)[^2]。 2. **查询阶段**: - 给定查询$(u, v)$,先将较深的节点向上跳到与另一个节点相同深度(使用二进制提升)。 - 然后,两个节点一起向上跳,直到它们的祖先相同。具体地,从最大$j$开始尝试跳跃,如果跳跃后祖先不同才执行跳跃。 算法的时间复杂度: - 预处理:$O(n \log n)$,其中$n$是节点数。 - 单次查询:$O(\log n)$。 空间复杂度:$O(n \log n)$。 #### 实现代码 以下是用Python实现的倍增法在线LCA算法。代码包括树的构建、预处理和查询函数。假设树以邻接表形式存储,节点编号从1开始。 ```python import math from collections import defaultdict, deque class LCA: def __init__(self, n, root=1): self.n = n self.root = root self.graph = defaultdict(list) self.depth = [0] * (n + 1) self.anc = defaultdict(lambda: [0] * (self.max_j + 1)) # 祖先表 def add_edge(self, u, v): """添加树边""" self.graph[u].append(v) self.graph[v].append(u) def preprocess(self): """预处理:计算深度和祖先表""" # 初始化最大跳跃指数 max_j = floor(log2(n)) self.max_j = math.floor(math.log2(self.n)) + 1 # BFS计算深度和直接父节点 queue = deque([self.root]) self.depth[self.root] = 1 parent = [0] * (self.n + 1) # 直接父节点 while queue: u = queue.popleft() for v in self.graph[u]: if v == parent[u]: continue parent[v] = u self.depth[v] = self.depth[u] + 1 queue.append(v) # 构建祖先表 anc[u][j] for j in range(self.max_j + 1): for i in range(1, self.n + 1): if j == 0: self.anc[i][j] = parent[i] # 2^0 = 1级祖先 else: prev = self.anc[i][j - 1] self.anc[i][j] = self.anc[prev][j - 1] if prev != 0 else 0 def query(self, u, v): """在线查询LCA(u, v)""" # 确保u较深,否则交换 if self.depth[u] < self.depth[v]: u, v = v, u # 将u跳到与v相同深度 diff = self.depth[u] - self.depth[v] j = 0 while diff: if diff & 1: # 二进制位为1时跳跃 u = self.anc[u][j] diff //= 2 j += 1 # 如果此时u和v相同,则v是LCA if u == v: return u # 一起向上跳,直到祖先相同 for j in range(self.max_j, -1, -1): if self.anc[u][j] != self.anc[v][j]: u = self.anc[u][j] v = self.anc[v][j] return self.anc[u][0] # LCA是u的直接父节点 # 示例用法 if __name__ == "__main__": # 构建树: 节点数n=5, 根节点为1 lca = LCA(n=5, root=1) edges = [(1, 2), (1, 3), (2, 4), (2, 5)] # 树边 for u, v in edges: lca.add_edge(u, v) lca.preprocess() # 预处理 # 在线查询示例 print(lca.query(4, 5)) # 输出: 2 (LCA of 4 and 5 is 2) print(lca.query(3, 4)) # 输出: 1 (LCA of 3 and 4 is 1) ``` #### 关键说明 - **在线处理**:此实现允许逐个添加查询(如`query(4, 5)`),无需预先知道所有查询,符合在线算法定义[^1]。 - **优化**:使用BFS代替DFS避免递归深度限制;二进制提升减少跳跃次数。 - **适用场景**:适用于动态查询环境,如实时网络路由或游戏AI中的路径查找。 如果您有特定树结构或查询序列,我可以帮助调整代码。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值