树上倍增求LCA

预备知识

倍增的思想:每次跳2^{i}步,是一种二进制的思想。

LCA(Lowest Common Ancestor):最近公共祖先,即两点往根节点走时遇到的第一个公共点。

(例如:LCA(2,5)=1,LCA(2,3)=1,LCA(3,5)=3)

离线处理一般用tarjan算法,时间复杂度为O(n+q).(n为结点数,q为询问数,下同)

(详见:https://blog.csdn.net/weixin_39872717/article/details/78493436

而在线处理,这里给出下面两种方法。

朴素的方法

根据定义,先将一个点到根节点路径上所有点处理出来,再从另一个点往根节点走,直到到达第一个走过的点,这个点就是它们的LCA,时间复杂度为O(nq).这种方法称为父链表回溯。

更优的方法

一个一个跳太慢了。

试想一下,如果树退化成一条链,那得多慢啊!

再说,用tarjan离线处理,O(n+q)都能求出所有LCA了;父链表回溯,是该淘汰了。

那么就用倍增的思想,每次跳2^i步吧!

可是i应该是多少呢?i太大,可能跳到的不是“最近”公共祖先了;i太小,又不是最近“公共祖先”了。

那么就先跳到相同深度吧!跳到相同深度后,它们就可以跳相同步数了。

等一下,怎么知道第2^i个祖先呢?

这里的预处理,又是用到倍增思想了,设fa[i][j]表示i结点的第2^j个祖先,显然有:fa[i][j]=fa[fa[i][j-1]][j-1].

预处理出fa[]数组,即:

for (int j=1;j<=int(log2(n));j++)
    for (int i=1;i<=n;i++)
        fa[i][j]=fa[fa[i][j-1]][j-1];

时间复杂度为O(nlogn).

接下来对于每次询问,先把深度大的点跳到与另一个点深度相同的点,即:

while (dep[x]>dep[y]) //假设x为深度大的结点
{
    int dis=int(log2(dep[x]-dep[y]));
    x=fa[x][dis];
}

之后呢?应该怎么跳?

假设我们跳2^i步跳到了一个相同的结点,那这个结点必定是它们公共祖先,但不一定最近;注意,不一定最近!

所以我们退而求其次,跳到最远的非公共祖先点,直到离LCA只差最后一步。

while (fa[x][0]!=fa[y][0]) //假设已跳到相同深度
{
    for (int i=int(log2(n));i>=1;i--) //这里其实也可以二分,不过没人这么无聊吧。。
           if (fa[x][i]!=fa[y][i])
           {
                x=fa[x][i];
                y=fa[y][i];
                break;
           }
}
if (fa[x][0]==fa[y][0]) return fa[x][0];

寻找LCA过程的时间复杂度为O(logn),故总时间复杂度为O(nlogn+qlogn).

  • 5
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值