树上倍增求LCA


考虑一个暴力解法:

一遍DFS预处理每一点的深度di
首先把 x, y 中深度较大的点向上移动,使得两点深度相等。 此时若 x, y 相等,则这个点就是所求的 LCA。
若 x, y 不等,则同时将 x, y 向上移动,直到两点重合为止。此时 重合点就是 LCA。
单次查询复杂度最坏 O(n)。

根据这种思路,容易写出代码

 1 int lca(int x,int y){
 2 
 3       if(d[x]<d[y])swap(x,y); //不妨设点x深度较大
 4 
 5       while(d[x]!=d[y])
 6 
 7         x=fa[x];
 8 
 9       if(x==y)return x;
10 
11       while(x!=y)
12 
13         x=fa[x],y=fa[y];
14 
15       return x;
16 
17 }

 

暴力解法的瓶颈在于节点向上跳的过程,可用倍增加速。
用f[x][i]表示x向上跳2i步后所处节点:

显然f[x][0]=fa[x]

递推关系:f[x][j]=f[f[x][i-1]][i-1]

利用 f 可以在 O(log n) 的时间内求出点 x 向上跳 k 步的结果。 
向上移动两点的操作也可以快速完成。
单次查询复杂度 O(log n)。

void dfs(int u,int fa){

      d[u]=d[fa]+1;

      f[u][0]=fa;

      for(int i=1;(1<<i)<=d[u];++i)

        f[u][i]=f[f[u][i-1]][i-1];

  for(int i=head[u];i;i=e[i].next){

    int v=e[i].to;

    if(v!=fa)

              dfs(v,u);

      }

}

 

关于lca函数的细节

2的16次方约为6e4,

20次方约为1e6,

k&(1<<i)意在取出数k中所有值为1的二进制位,自然地完成了拆分,例如23=10101(2)=16+4+1

最后一步必然跳到lca的子节点,故return f[x][0]即可。

int lca(int x,int y){

      if(d[x]<d[y])swap(x,y);

      int k=d[x]-d[y];

      for(int i=16;i>=0;--i){

            if(k&(1<<i))

              x=f[x][i];

      }

      if(x==y)return x;

      for(int i=16;i>=0;--i){

            if(f[x][i]!=f[y][i])

              x=f[x][i],y=f[y][i];

      }

      return f[x][0];

}

 

树上倍增的应用:

树上两点的距离:dis(u,v)=d(u)+d(v)-2d(lca(u,v))。

 
posted @ 2020-02-13 00:10  _vv123  阅读(57)  评论(0)    收藏  举报