这次没能讲约瑟夫问题,实在是因为我还没有把它的线段树做法吃透,所以只好先为大家带来一个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 }
附上刚刚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 }
由于本人也是刚刚学,以后会给大家细讲一下倍增(RMQ)优化后的LCA,那样时间复杂度就会降低到O(logn),就更低了。
好了,这次的LCA讲解就到这里了。本coding的小钢钉还是建议大家多做题多做题多做题、自己做自己做自己做,以后我会为大家附上图片讲解,让讲解更加生动。