倍增的思是二进制,二进制需要进行位运算
首先开一个n×logn的数组,比如fa[n][logn],其中fa[i][j]表示i节点的第2^j个父亲是谁。
然后,我们会发现有这么一个性质: fa[i][j]=fa [fa[i][j-1]] [j-1]
用文字叙述为:i的第2^j个父亲 是 i的第2^(j-1)个父亲的第2^(j-1)个父亲。
个人理解这个性质 认为 : 2^j = 2^(j-1) + 2^(j-1)
求 i 的第K个父亲 需要将K拆分分成多个满足 2^n 的数相加
int father(int i,int k) { for(int x=0;x<=int(log2(k));x++) if((1<<x)&k) //(1<<x)&k可以判断k的二进制表示中,第x位上是否为1 ,1左移x位与K的x位 进行与(&)计算。
i=fa[i][x]; //把i往上 加一个k满足的2^n, 当循环结束,所有的2^n都加上,即找到了第K个父亲。 return i; }
用树上倍增的时候,用dfs预处理出 每一个数的所有的父节点,即fa[n][logn] 数组,便于后面查询父节点。
如果待处理的树有n个节点,那么最多有一个节点会有2^(logn)个父亲,所以我们的fa数组第二维开logn就够了。
(depth[i]表示i的深度,这个可以一起处理出来,以后要用)
//nowp=nowposition fa=father void dfs(int nowp,int fa){ depth[nowp] =depth[fa]+1; father[nowp][0]=fa; for(int j=1;j <=lg[depth[nowp]]+1;++j) father[nowp][j]=father [father[nowp][j-1]][j-1]; for(int i=0;i<G[nowp].size();++i){ if(G[nowp][i]!=fa) dfs(G[nowp][i],nowp); } }
这样,我们在nlogn的时间内可以通过一遍dfs处理出这棵树的相关信息。 倍增的应用中,最基础的应该就是求LCA(最近公共祖先),时间复杂度是logn。
对于求u、v的LCA,我们可以先把u、v用倍增法把深度大的提到和另一个深度相同。如果此时u、v已经相等了,表示原来u、v就在一条树链上,直接返回此时的结果。
如果此时u、v深度相同但不等,则证明他们的lca在更“浅”的地方,此时需要把u、v一起用倍增法上提到他们的父亲相等。为啥是提到父亲相等呢?因为倍增法是一次上提很多,所以有可能提“过”了,如果是判断他们本身作为循环终止条件,就无法判断是否提得过多了,所以要判断他们父亲是否相等。
int LCA(int u,int v){ if(depth[u]<depth [v]) swap(u,v); //int dc = depth[u]-depth[v]; // for(int j=0;j<maxbit;++j) // { // if((dc>>j)&1) u=father[u][j]; // } while(depth[u]!=depth[v]){ u=father[u][lg[depth[u]-depth[v]]]; } if(u ==v) return u; for(int j= lg[depth[u]];j>=0;--j){ if(father[u][j]!=father[v][j]){ u=father[u][j]; v=father[v][j]; } } return father[u][0]; }
树上倍增的真正意义
例题: 给你一棵树和两个点x和y,求这两点间路径的路径最大/小的点权/边权
我们可以设dis[i][j] 表示i到他的第2^j 个祖先的路径最大值,就可以边求LCA 边顺便求出两点距离
还有求两点的路径上路径权值和呢?
设sum[i][j] 表示i到他的第2^j个祖先的路径权值和,同时也可边求LCA边求和,
因为 sum[i][j]=sum[i][j−1]+sum[anc[i][j−1]][j−1])
像RMQ求LCA ,是无法解决这类问题的
参考链接:
https://blog.csdn.net/saramanda/article/details/54963914
https://blog.csdn.net/enjoy_pascal/article/details/78277008