lca(最近公共祖先(在线)) 倍增法详解

转自大佬博客 : https://blog.csdn.net/lw277232240/article/details/72870644

描述:倍增法用于很多算法当中,通过字面意思来理解

LCA是啥呢  在一棵树当中 lca表示的是两个节点最近公共祖先, 大家看这课树哈节点5 ,3的lca就是1,13和11的LCA就是6。节点8,12的lca就是8,那么我们如何通过被增来实现LCA呢。

首先请大家认真看下面的分析。

depth[x],表示x节点的深度。

大家看下这个数组 grand[x][i] ,这个数组表示标号为x节点向上跳2^i步的节点。例如grand[5][0]=2,因为5这个节点向上跳2^0次方个就是2,当然所有节点向上跳一个就是他的父亲节点,

得到grand[x][0]=他的爸爸,

那grand[x][1]=什么呢?等于grand[grand[x][0]][0];

既grand[x][i]=grand[grand[x][i-1]][i-1],为什么呢,2^4=2^3+2^3,表示 x向上跳2的i次的节点=x向上跳i-1次方的这个节点向上跳i-1次方,有点绕口。例如 grand[13][2]=grand[6][2]=g[g[rand][i-1]][i-1];

有了这个预处理那我们是不是可以得到每一个节点向上跳i格呢,然后就是i的取值范围为1<=i<=log2(n),向下取整n表示节点个数,也就是说grand[x][log2(n)]就至少可以跳到根节点甚至上面,当然上面啥都没有。

我们这里还有一个数组 gw[x][i],表示x节点向上跳2^i次方的距离(权)。同样的有gw[x][i]=gw[x][i-1]+gw[grand[x][i-1]][i-1],这个公式表示x到他向上跳2的i次方那个节点的距离=x跳2^i-1次方的距离加上,他向上跳2^i-1次方这个点到他上面跳2^i-1次方的距离。

例如1,3,6,10,13,他们之间的边的权等于 2 4 3 6,既gw[13][0]=他爸爸10到他的距离等于6,gw[]13[2]=gw[13][1]+gw[6][1];。

其实我们可以自己加很多类似的数组

例如max[x][i]啊表示 x向上跳2^i次方的边的最大的那条边。这里就不一一举例子子了

现在有了这些预处理。我下面是用dfs实现的预处理;

就看如何实现LCA了吧,

首先 给定两个节点。假如给定的是a=13,b=12.。要我们求他们的lca ,首先第一步 我们要把他们调整到同一深度,既把a向上跳一个步这样他们的深度就相同的了。我们是把深度高的向上调。调整跟深度低的一样。

然后就两个节点一起向上跳一个2^j,首先我们j从log2(n)=3开始跳,跳到0。我们要跳到他们lca的下面两个节点。既3和4,既可以跳的节点是他们不相同并且不超出根节点(grand[a][j]!=grand[b][j]),这里是j=1的时候跳了,j=1因为(grand[a][j]!=grand[b][j]);就执行(a=grand[a][j],b=grand[b][j])a就跳到了3,b就跳到了4,其余的j就不可以跳因为j=3,2就到根的上面了j=0他们就想等了。跳到那,最后退出,grand[a][0]就是他们的lca了。

这就是程序的执行过程。下面看代码

#include<stdio.h>
#include<string.h>
#include<vector>
#include<stdlib.h>
#include<math.h>
#define max 40001
#define maxl 25
using namespace std;
typedef struct
{
    int from,to,w;
}edge;//这个结构体用来存储边

vector<edge>edges;
vector<int> G[max];
//保存边的数组
int grand[max][maxl],gw[max][maxl];//x向上跳2^i次方的节点,x到他上面祖先2^i次方的距离
int depth[max];//深度
int n,m,N;
void addedge(int x,int y,int w)//把边保存起来的函数
{
   edge a={x,y,w},b={y,x,w};
   edges.push_back(a);
   edges.push_back(b);

   G[x].push_back(edges.size()-2);
   G[y].push_back(edges.size()-1);
}
void dfs(int x)//dfs建图
{
    for(int i=1;i<=N;i++)//第一个几点就全部都是0咯,第二个节点就有变化了,不理解的话建议复制代码输出下这些数组

    {
        grand[x][i]=grand[grand[x][i-1]][i-1];
        gw[x][i]=gw[x][i-1]+gw[grand[x][i-1]][i-1];
       // if(grand[x][i]==0) break;
    }

    for(int i=0;i<G[x].size();i++)
    {   edge  e = edges[G[x][i]];
         if(e.to!=grand[x][0])//这里我们保存的是双向边所以与他相连的边不是他父亲就是他儿子父亲的话就不能执行,不然就死循环了。

           {
                depth[e.to]=depth[x]+1;//他儿子的深度等于他爸爸的加1
                grand[e.to][0]=x;//与x相连那个节点的父亲等于x
                gw[e.to][0]=e.w;//与x相连那个节点的距离等于这条边的距离
                dfs(e.to);//深搜往下面建
           }
    }
}
void Init(){
    //n为节点个数
    N = floor(log(n + 0.0) / log(2.0));//最多能跳的2^i祖先
    depth[1]=0;//根结点的祖先不存在,用-1表示
    memset(grand,0,sizeof(grand));
    memset(gw,0,sizeof(gw));
    dfs(1);//以1为根节点建树

}
int lca(int a,int b)
{ if(depth[a] > depth[b]) swap(a, b);//保证a在b上面,便于计算
    int ans = 0;
    for(int i = N; i >= 0; i--) //类似于二进制拆分,从大到小尝试
        if(depth[a] < depth[b] && depth[grand[b][i]] >= depth[a])//a在b下面且b向上跳后不会到a上面
            ans +=gw[b][i], b=grand[b][i];//先把深度较大的b往上跳

    for(int j=N;j>=0;j--)//在同一高度了,他们一起向上跳,跳他们不相同节点,当全都跳完之后grand【a】【0】就是lca,上面有解释哈。
    {
        if(grand[a][j]!=grand[b][j])
        {   ans+=gw[a][j];
            ans+=gw[b][j];
            a=grand[a][j];
            b=grand[b][j];

        }
    }
    if(a!=b)//a等于b的情况就是上面土色字体的那种情况
    {
        ans+=gw[a][0],ans+=gw[b][0];
    }
    return ans;
}
int main()
{ int t ;
  scanf("%d",&t);
  while(t--)
  {   scanf("%d%d",&n,&m);
      for(int i=1;i<n;i++)
      {
          int x,y,w;
          scanf("%d%d%d",&x,&y,&w);
          addedge(x,y,w);
      }
     Init();
     for(int i=1;i<=m;i++)
     {
         int x,y;
         scanf("%d%d",&x,&y);
         printf("%d\n",lca(x,y));
     }
  }
}

原文地址:https://www.cnblogs.com/fzw1523/p/10375213.html

时间: 2024-10-05 05:22:25

lca(最近公共祖先(在线)) 倍增法详解的相关文章

LCA最近公共祖先 在线算法和离线算法 模板

原理讲解:http://dongxicheng.org/structure/lca-rmq/ 在线算法模板: /** LCA在线算法O(nlogn) 主函数调用: init(); tot=0,dir[1]=0; dfs(1,1); ST(2*n-1); int lca=LCA(u,v); */ #include <stdio.h> #include <string.h> #include <algorithm> #include <iostream> #in

树,LCA,最近公共祖先,倍增

最近公共祖先, 树上倍增,LCA, fa [ i ] [ j ] 表示 i 节点向上 2j 的祖先 很像dp, k 属于 [ 1 , log ( n ) ] ,f [ x ][ k ] = f [ f [ x ] [ k-1 ]] [k-1] 算lca时, 先不妨设 d [ x ] >= d [ y ] 二进制拆分 尝试从x 向上走 k = 2log (n),~~,21,20, 检查到的点是否比 y 深 每次检查中,若是,x= f [ x ] [ k ] 若仍未相会, 即 f [ x ] [ k

笔记:LCA最近公共祖先 Tarjan(离线)算法

LCA最近公共祖先 Tarjan他贱(离线)算法的基本思路及其算法实现 本文是网络资料整理或部分转载或部分原创,参考文章如下: https://www.cnblogs.com/JVxie/p/4854719.html http://blog.csdn.net/ywcpig/article/details/52336496 https://baike.baidu.com/item/最近公共祖先/8918834?fr=aladdin 最近公共祖先简称LCA(Lowest Common Ancesto

lca 最近公共祖先

http://poj.org/problem?id=1330 1 #include<cstdio> 2 #include<cstring> 3 #include<algorithm> 4 #define mt(a,b) memset(a,b,sizeof(a)) 5 using namespace std; 6 const int inf=0x3f3f3f3f; 7 class LCA { ///最近公共祖先 build_o(n*logn) query_o(1) 8 t

Codeforces Round #362 (Div. 2) C. Lorenzo Von Matterhorn LCA(最近公共祖先)

C. Lorenzo Von Matterhorn time limit per test 1 second memory limit per test 256 megabytes input standard input output standard output Barney lives in NYC. NYC has infinite number of intersections numbered with positive integers starting from 1. Ther

POJ 1330 LCA最近公共祖先 离线tarjan算法

题意要求一棵树上,两个点的最近公共祖先 即LCA 现学了一下LCA-Tarjan算法,还挺好理解的,这是个离线的算法,先把询问存贮起来,在一遍dfs过程中,找到了对应的询问点,即可输出 原理用了并查集和dfs染色,先dfs到底层开始往上回溯,边并查集合并 一边染色,这样只要询问的两个点均被染色了,就可以输出当前并查集的最高父亲一定是LCA,因为我是从底层层层往上DSU和染色的,要么没被染色,被染色之后,肯定就是当前节点是最近的 #include <iostream> #include <

网络双绞线4根线接法详解(水晶头RJ45)

网络双绞线4根线接法详解 一直以来很多人都认为10 Base-T 10M网络使用了网线中8条信号线之4条,而100 Base-T 100M则使用了全部8条信号线(要不怎么那么快呢?).可是作者前不久在使用一条按所谓10M直连接法(1与3.2与6交换,其余四线接外壳屏蔽)接出的网线时,意外地发现网络正以100M高速传输,百思不得其解,于是上网查阅了大量资料,加上好几台机实验验证,终于发现了事实真相,那就是,100M的双绞线与10M的标准接法完全是一样!     双绞线接头(RJ45)针脚号码定义

c?#?中 ?s?o?c?k?e?t? ?、?T?C?P?C?l?i?e?n?t?、?T?C?P?L?i?s?t?e?n?e?r? ?用?法?详?解

Visual C#.Net网络程序开发-Socket篇 Microsoft.Net Framework为应用程序访问Internet提供了分层的.可扩展的以及受管辖的网络服务,其名字空间System.Net和System.Net.Sockets包含丰富的类可以开发多种网络应用程序..Net类采用的分层结构允许应用程序在不同的控制级别上访问网络,开发人员可以根据需要选择针对不同的级别编制程序,这些级别几乎囊括了Internet的所有需要--从socket套接字到普通的请求/响应,更重要的是,这种分

分支界定法详解

分支界定法是求解整数线性规划最优解的经典方法. 定义: 对有约束条件的最优化问题(其可行解为有限数)的所有可行解空间恰当地进行系统搜索,这就是分支与界定的内容.通常把全部解空间反复地分割为越来越小的子集,称为分枝:并对每个子集内的解集计算一个目标下界(对于最小值问题),这称为定界.在每次分枝后,若某个已知可行解集的目标值不能达到当前的界限,则将这个子集舍去.这样,许多子集不予考虑,这称为剪枝.这就是分枝界限法的思路. 背景: 分枝界限法可以用于求解纯整数或混合的整数规划问题.在上世纪六十年代由L