提到树上倍增就不得不先说说最近公共祖先(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 ?