LCA(最近公共祖先问题)

本文通过实际问题引入LCA(最近公共祖先)算法,详细解析了如何使用LCA解决树状结构中的两点间距离问题,并提供了求解两点距离的高效算法。文章最后预告了后续将讲解更优化的LCA算法,旨在帮助读者掌握这一实用的数据结构知识。
摘要由CSDN通过智能技术生成

这次没能讲约瑟夫问题,实在是因为我还没有把它的线段树做法吃透,所以只好先为大家带来一个LCA问题的博客。

某些朋友告诉我,要理论联系实际。那么今天我们就从一个实际问题出发,研究LCA问题,题目如下:

2370 小机房的树

 时间限制: 1 s      空间限制: 256000 KB     题目等级 : 钻石 Diamond

题目描述 Description

小机房有棵焕狗种的树,树上有N个节点,节点标号为0到N-1,有两只虫子名叫飘狗和大吉狗,分居在两个不同的节点上。有一天,他们想爬到一个节点上去搞基,但是作为两只虫子,他们不想花费太多精力。已知从某个节点爬到其父亲节点要花费 c 的能量(从父亲节点爬到此节点也相同),他们想找出一条花费精力最短的路,以使得搞基的时候精力旺盛,他们找到你要你设计一个程序来找到这条路,要求你告诉他们最少需要花费多少精力

输入描述 Input Description

第一行一个n,接下来n-1行每一行有三个整数u,v, c 。表示节点 u 爬到节点 v 需要花费 c 的精力。
第n+1行有一个整数m表示有m次询问。接下来m行每一行有两个整数 u ,v 表示两只虫子所在的节点

输出描述 Output Description

一共有m行,每一行一个整数,表示对于该次询问所得出的最短距离。

样例输入 Sample Input

3

1 0 1

2 0 1

3

1 0

2 0

1 2

样例输出 Sample Output

1

1

2

 

数据范围及提示 Data Size & Hint

1<=n<=50000, 1<=m<=75000, 0<=c<=1000

光看题目的话,大家可能是第一时间想到的是暴力搜索,dfs或者BFS,但是进入过竞赛的人皆知,基本上暴力是拿不到全分的,所以此题我们来介绍一种O(n^2+m)的算法(实际上是因为题目的测试数据过于水了,不然O(n^2+m)的复杂度怎么可能过),在此之前,我们介绍一种求树上两点距离的在线O(n)算法——LCA最近公共祖先:

LCA的工作原理基本如下:

再输入过程中初始化所有点到根节点的路径长(当然题目如果没要求求路经长的话可以忽略路经长,但一般是要求的),并计算该点的深度,从深度上进行操作:

假设我们求的是a、b两点的LCA,即LCA(a,b),那么我们这时候要判断a,b两点的深度,让深度大的那个往上走,移动到他父亲节点的位置上,深度变浅(注意就算是要求两点距离,也不必要在这时候进行操作记录),直到两点处在同一深度上;这时候开始循环直到两点重合为一点时停止,如果不重合那么两点都往上走,都移到相应的它们父亲节点的位置上。退出循环后,这时两点重合,这个点即是LCA(a,b)。这时候我们就求完了。

接下来我们说一下求两点之间的距离的求法:

假设我们用dist数组表示某个节点到树的根节点的距离,则ans = dist[a]+dist[b]-2*dist[LCA(a,b)];具体证明请读者去画图验证,其实证明真的很简单,自己画画图就好了,用用图论的知识就好了。

朴素LCA核心代码如下:

 1 int lca(int a,int b){
 2     while(d[a] > d[b]){
 3         a = f[a];
 4     }
 5     while(d[a] < d[b]){
 6         b = f[b];
 7     }
 8     while(d[a] == d[b] && a != b){
 9         a = f[a];
10         b = f[b];
11     }
12     if(a == b){
13         return a;
14     }
15 }
View Code

附上刚刚2370号题目的题解,只是一个比较朴素的LCA,时间复杂度还是下不来啊……

 1 #include <iostream>
 2 #include <cstring>
 3 #include <cmath>
 4 #include <vector>
 5 using namespace std;
 6 int n,m,u,v,c,p,q;
 7 int dist[50001],d[50001];
 8 bool vi[50001];
 9 int f[50001];
10 int lca(int a,int b){
11     while(d[a] > d[b]){
12         a = f[a];
13     }
14     while(d[a] < d[b]){
15         b = f[b];
16     }
17     while(d[a] == d[b] && a != b){
18         a = f[a];
19         b = f[b];
20     }
21     if(a == b){
22         return a;
23     }
24 }
25 int main(){
26     memset(dist,0,sizeof(dist));
27     memset(d,0,sizeof(d));
28     d[1] = 1;
29     memset(vi,false,sizeof(vi));
30     cin >> n;
31     vi[1] = true;
32     for(int i = 1;i < n;++i){
33         cin >> u >> v >> c;
34         u++;
35         v++;
36         if(!vi[v])swap(u,v);
37         vi[u] = 1;
38         f[u] = v;
39         dist[u] = dist[v] + c;
40         d[u] = d[v] + 1;
41     }
42     cin >> m;
43     for(int i = 1;i <= m;++i){
44         cin >> p >> q;
45         p++;q++;
46         int x = lca(p,q);
47         int ans = dist[p] + dist[q] - (2*dist[x]);
48         cout << ans << endl;
49     }
50     return 0;
51 }
View Code

由于本人也是刚刚学,以后会给大家细讲一下倍增(RMQ)优化后的LCA,那样时间复杂度就会降低到O(logn),就更低了。

好了,这次的LCA讲解就到这里了。本coding的小钢钉还是建议大家多做题多做题多做题、自己做自己做自己做,以后我会为大家附上图片讲解,让讲解更加生动。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值