zoukankan      html  css  js  c++  java
  • LCA树上倍增求法

    1.LCA

           LCA就是最近公共祖先(Least common ancestor),x,y的LCA记为z=LCA(x,y),满足z是x,y的公共祖先中深度最大的那一个(即离他们最近的那一个)qwq

    2.问题引入

           看LCA之前最好学一下并查集,因为这两个东西有点相似,不同之处在于并查集一旦进行了路径压缩,便只能求出两个点之间是否存在关系,无法精确判断谁是谁的祖先以及两者的深度最大的公共祖先(只能判断有没有公共祖先)。

    但LCA就不一样了,他可以实现并查集的操作,还可以查询两者的最近祖先,emm,关于这一点的应用嘛,就是比如说求树上最短路的问题,LCA会方便很多

    3.LCA倍增算法本法

            LCA的雏形就是单纯地考虑x,y同时向自己的爸爸跳,直到相遇,但如果是两条链连在树根上,x,y又分别是两个叶子结点的话。。。。会很慢很慢

            那么问题来了:怎么优化???

             不知道大家有没有想过我们为什么要使用树形结构?单纯是为了好看嘛?显然不是的。个人认为树形结构的优点之一在于:其特有的结构在搜索时可以极大地缩减深度,避免了一些不必要的试探

    从而节约了时间。那么,当一棵树退化为一条链的时候,这个优点便不那么明显了,甚至会退化为普通的搜索算法。问题的关键在于我们在搜索时是一步一步地向前试探,但如果存在x,y的深度相差很

    大的情况,那么,两个结点回溯到深度都为dep的祖先之前的试探其实都是无效的,这也是普通算法低效的原因所在。那么我们可不可以一次向上多跳几步呢?如果可以,我们应该怎么表示这种状态呢?

    这里介绍一种倍增的思想,即每次向上跳2^k步,其中k为大于等于0的整数。不妨设x跳了2^k步后到达的结点为z,如果dep[z]>=dep[y],那么证明这一步试探也是无效的,但是否说明这个状态没有用呢?

    其实不然。类似于快速幂的算法,x向上跳2^k步,其实等价于x先向上跳2^(k-1)步再向上跳2^(k-1)步,这也是我们为什么选择向上跳2的整数次幂步的原因,这是一种极其高效的方法,而且便于处理。

    那么,我怎么知道向上跳2^k步后到达了哪个结点呢?这时候就需要预处理,不妨设f[x][k]表示x结点向上跳2^k步后所到达的结点,特别的,f[x][0]表示x的父结点那么由上述推论:f[x][k]=f[f[x][k-1]][k-1]

    .然后我们再递归地向下处理。这就是预处理的部分.

    然后,我们只需要先让x跳到与y深度相同(这里默认x的深度大于y),然后,x,y再同时向上跳,直到x,y相遇前的最后一步,那么,此时f[x][0]==f[y][0]==LCA(x,y)

    整个算法也就结束啦,下面直接上代码:

    题目:

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

    输入

    第一行一个正整数 n ,表示这棵树有 n个节点;

    接下来 n−1 行,每行两个整数 x,y 表示 x,y 之间有一条连边;

    然后一个整数 Q,表示有Q 个询问;

    接下来 Q 行每行两个整数x,y 表示询问 x 到 y 的距离。

    输出

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

    样例输入
    6
    1 2
    1 3
    2 4
    2 5
    3 6
    2
    2 6
    5 6
    样例输出
    3
    4
    提示

    对于全部数据,1n,m105,1x,yn

     

    程序来啦QAQ:

      1 #include<iostream> 
      2 #include<cstdio>
      3 #include<algorithm>
      4 #include<cstdlib>
      5 #define ONE 100010
      6 
      7 using namespace std;
      8 
      9 struct node {
     10     int u;
     11     int v;
     12     int next;
     13 };
     14 
     15 node edge[ONE<<1];  //邻接表存数据 
     16 int dep[ONE],f[ONE][30],next[ONE],cnt_edge=0,n,q,x,y;   
     17 
     18 //int Read()   快读可以加速,可以学一下 
     19 //{
     20 //    int f=1,k=0;
     21 //    char c=getchar();
     22 //    while(c!='-'&&(c<'0'||c>'9')) c=getchar();
     23 //    if(c=='-')
     24 //    {
     25 //        f=-1;
     26 //        c=getchar();
     27 //    }
     28 //    while(c>='0'&&c<='9')
     29 //    {
     30 //        k=(k<<3)+(k<<1)+c-48;
     31 //        c=getchar();
     32 //    }
     33 //    return f*k;
     34 //}
     35 
     36 void add_edge(int u,int v)
     37 {
     38     edge[++cnt_edge].u=u;
     39     edge[cnt_edge].v=v;
     40     edge[cnt_edge].next=next[u];
     41     next[u]=cnt_edge; 
     42 }
     43 
     44 void Deal_first(int u,int fa)//预处理 
     45 {
     46     dep[u]=dep[fa]+1;//处理当前结点深度,方便后面向上跳时使用 
     47     
     48     for(int i=0;i<=19;i++) f[u][i+1]=f[f[u][i]][i];//类似于二分的思想 
     49     
     50     for(int i=next[u];i;i=edge[i].next)
     51     {
     52         int to=edge[i].v;
     53         if(to==fa) continue;//注意判断回边,不能跳到自己的父亲结点 
     54         f[to][0]=u;
     55         Deal_first(to,u);//向下递归 
     56     }
     57 }
     58 
     59 int LCA(int x,int y)  //求解LCA 
     60 {
     61     if(dep[x]<dep[y]) swap(x,y); //保证x的深度大于y的深度 
     62     for(int i=20;i>=0;i--)        //这里注意要先跳大步再跳小步,类似于用天平称量物体时先放大砝码再放小砝码是一个道理 
     63     {
     64         if(dep[f[x][i]]>=dep[y]) x=f[x][i];
     65         
     66         if(x==y) return x;
     67     }
     68     for(int i=20;i>=0;i--)
     69     {
     70         if(f[x][i]!=f[y][i])
     71         {
     72             x=f[x][i];
     73             y=f[y][i];
     74         }
     75     }
     76     return f[x][0];
     77 }
     78 
     79 int get_dis(int x,int y)
     80 {
     81     return  dep[x]+dep[y]-2*dep[LCA(x,y)];
     82 }
     83 
     84 int main ()
     85 {
     86     //n=Read();
     87     scanf("%d",&n);
     88     for(int i=1;i<n;i++)
     89     {
     90         //x=Read();y=Read();
     91         scanf("%d%d",&x,&y);
     92         add_edge(x,y);
     93         add_edge(y,x);
     94     }
     95     
     96     Deal_first(1,0);
     97 
     98     //q=Read();
     99     scanf("%d",&q);
    100     while(q--)
    101     {
    102 //        x=Read();
    103 //        y=Read();
    104         scanf("%d%d",&x,&y);
    105         printf("%d
    ",get_dis(x,y));
    106     }
    107     return 0;
    108 }

    看完了点个赞再走哦亲~(手动开心o( ̄▽ ̄)o)

  • 相关阅读:
    读书笔记——吴军《态度》
    JZYZOJ1237 教授的测试 dfs
    NOI1999 JZYZOJ1289 棋盘分割 dp 方差的数学结论
    [JZYZOJ 1288][洛谷 1005] NOIP2007 矩阵取数 dp 高精度
    POJ 3904 JZYZOJ 1202 Sky Code 莫比乌斯反演 组合数
    POJ2157 Check the difficulty of problems 概率DP
    HDU3853 LOOPS 期望DP 简单
    Codeforces 148D. Bag of mice 概率dp
    POJ3071 Football 概率DP 简单
    HDU4405 Aeroplane chess 飞行棋 期望dp 简单
  • 原文地址:https://www.cnblogs.com/Roysblog/p/13762081.html
Copyright © 2011-2022 走看看