LCA (求最近公共祖先)

树上倍增求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;
        }//输出
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

El.十一

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值