树上倍增

提到树上倍增就不得不先说说最近公共祖先(LCA)了

如下图所示

④和⑤的LCA即为②(绿色的)

那怎么求LCA呢?

最简单粗暴的方法就是先深搜一次,处理出每个点的深度

然后把深度更深的那一个点④一个点地一个点地往上跳,直到到某个点③和另外那个点⑤的深度一样

然后两个点一起一个点地一个点地往上跳,直到到某个点(就是LCA)时两个点重合

不过大家应该发现一个点地一个点地跳时间复杂度会很高    

如果一下子跳到目标点内存又不支持

所以神犇们找到了一种新的算法--树上倍增

时间复杂度为O(n*logn)

树上倍增思路

先比较两个点的深度,如果深度不同,先让深的点往上跳,浅的先不动,等两个点深度一样时,如果是同一点直接返回,如果是不同点进行下一步:如果不同,两个点一起跳,j从大到小枚举(j不用太大),如果两个点都跳这么多后,得到的点相等,两个点都不动(因为有可能正好是LCA也有可能在LCA上方),直到得到的点不同,就可以跳上来,然后不断跳,因此两个点都在LCA下面那层,所以再跳1步即可到达LCA

整体思路图

状态转移方程

f[i][j]表示节点i往上跳2^j次后的节点 
可以转移为 
f[i][j] = f[f[i][j-1]][j-1]

(此处注意循环时先循环j,再循环i)

至于为啥自行理解

此处是核心代码

 1 int lca(int x,int y){
 2     if(dep[x] < dep[y]) swap(x,y);//保证x比y深
 3     for(int i = 20;i >= 0;i--)
 4         if(dep[x] - (1<<i) >= dep[y]) x = fa[x][i];//深的先跳
 5              //1<<i表示把1向左移i位,类似于2的累乘器
 6     if(x == y) return x;
 7       //若深的跳完刚好与浅的那个重合,则直接输出
 8     for(int i = 20;i >= 0;i--)
 9         if(fa[x][i] != fa[y][i])
10     {
11             x = fa[x][i];
12             y = fa[y][i];
13         }
14     return fa[x][0];//0 是再跳一步
15 }

树上倍增还可以有很多变化,这使得它可以有更多的变化。

可以用来维护各结点的各个数据

比如用l[i][j]记录i到他的第2^j个父亲的路径长度,就可以边求LCA边求出两点距离,因为l[i][j]满足倍增的递推式:

l[i][j] = l[i][j-1] + l[fa[i][j-1]][j-1]

或者用maxlen[i][j]记录i到第2^j个父亲的路径上最长边的边权,它满足

maxlen[i][j] = max{maxlen[i][j-1], maxlen[fa[i][j-1]][j-1]},这样就可以快速求出两点路径上最长边的边

例题!!!

洛谷P3379 【模板】最近公共祖先(LCA)

https://www.luogu.org/problem/show?pid=3379

POJ 1986 Distance Queries

http://poj.org/problem?id=1986

HDU 3078 Network

http://acm.hdu.edu.cn/showproblem.php?pid=3078

HDU 2586 How far away ?

http://acm.hdu.edu.cn/showproblem.php?pid=2586

原文地址:https://www.cnblogs.com/thx666/p/8495218.html