树上倍增求LCA(在线算法)
预处理:
- dfs一遍,找到每个节点的 所有 2次幂的祖先。 f[i][j] 表示 i 节点的 2^j 级祖先。
- 通过递推预处理出 log2(i)+1 。
// h[x] 为 x节点得到深度 f[i][j]与上面一样
//
void dfs(int x,int fa){
h[x]=h[fa]+1; f[x][0]=fa;
for(int i=1;(1<<i)<=h[x];i++) f[x][i]=f[f[x][i-1]][i-1];//意思是f的2^i祖先等于f的2^(i-1)祖先的2^(i-1)祖先
for(int i=head[x];i;i=nex[i]){
if(to[i]!=fa) dfs(to[i],x);
}
}
for (int i = 1; i <= n; i++)
lg[i] = lg[i - 1] + (1 << lg[i - 1] == i);
然后就是具体求LCA了:
我们先把两个点提到同一高度,再统一开始跳。要跳到它们LCA的下面一层,然后输出它们的父节点.
int LCA(int x, int y) {
//设x结点为深层节点
if (deep[x] < deep[y])swap(x, y);
//将x拉到与y等深度
while (deep[x] > deep[y])
x = fa[x][lg[deep[x] - deep[y]] - 1];
//此时如果结点一样,则找到
if (x == y)
return x;
//如果不一样,一步一步拉x y,从大距离到小距离枚举
for (int k = lg[deep[x]]; k >= 0; --k) {
if (fa[x][k] != fa[y][k])
{
x = fa[x][k];
y = fa[y][k];
}
return fa[x][0];
}
}
Tarjan求LCA(离线)
将当前结点标记为已经访问。
递归遍历所有它的子节点(称之为 y),并在递归执行完后用并查集合并 x 和 y。
遍历与当前节点有查询关系的结点(称之为 z)(即是需要查询 LCA 的另一些结点),如果 z 已经访问,那么 x 与 z 的 LCA 就是 getfa(z)(这个是并查集中的查找函数),输出或者记录下来就可以了。
因为,每一个节点x是在子节点递归后完成合并的,所以在查询关系获取父节点时,x的fa[]还未更新,并且x的父节点z也没有更新fa[],如果另一查询的节点y的已经访问(则已经更新fa[]),那么fa[y]即为 LCA。
并查集操作:
int fa[100000];
void reset(){
for (int i=1;i<=100000;i++){
fa[i]=i;
}
}
int getfa(int x){
return fa[x]==x?x:getfa(fa[x]);
}
void marge(int x,int y){
fa[getfa(y)]=getfa(x);
}
举例:比如4与5,先dfs遍历到4,此时5没访问过,则不能求LCA,dfs遍历到5时,此时fa[2]=2, fa[5]=5, fa[4]=2。
void tarjan(int x){
v[x]=1;//标记已访问
node p=s[x];//获取当前结点结构体
if (p.l!=-1){
tarjan(p.l);
marge(x,p.l);
}
if (p.r!=-1){
tarjan(p.r);
marge(x,p.r);
}//分别对l和r结点进行操作
for (int i=1;i<=top[x];i++){
if (v[t[x][i]]){
cout<<getfa(t[x][i])<<endl;
}//输出
}
}