树上倍增求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))。

浙公网安备 33010602011771号