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)

原文地址:https://www.cnblogs.com/Roysblog/p/13762081.html