预备知识
倍增的思想:每次跳步,是一种二进制的思想。
LCA(Lowest Common Ancestor):最近公共祖先,即两点往根节点走时遇到的第一个公共点。
(例如:LCA(2,5)=1,LCA(2,3)=1,LCA(3,5)=3)
离线处理一般用tarjan算法,时间复杂度为.(
为结点数,
为询问数,下同)
(详见:https://blog.csdn.net/weixin_39872717/article/details/78493436)
而在线处理,这里给出下面两种方法。
朴素的方法
根据定义,先将一个点到根节点路径上所有点处理出来,再从另一个点往根节点走,直到到达第一个走过的点,这个点就是它们的LCA,时间复杂度为这种方法称为父链表回溯。
更优的方法
一个一个跳太慢了。
试想一下,如果树退化成一条链,那得多慢啊!
再说,用tarjan离线处理,都能求出所有LCA了;父链表回溯,是该淘汰了。
那么就用倍增的思想,每次跳步吧!
可是应该是多少呢?
太大,可能跳到的不是“最近”公共祖先了;
太小,又不是最近“公共祖先”了。
那么就先跳到相同深度吧!跳到相同深度后,它们就可以跳相同步数了。
等一下,怎么知道第个祖先呢?
这里的预处理,又是用到倍增思想了,设表示
结点的第
个祖先,显然有:
预处理出数组,即:
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];
时间复杂度为
接下来对于每次询问,先把深度大的点跳到与另一个点深度相同的点,即:
while (dep[x]>dep[y]) //假设x为深度大的结点
{
int dis=int(log2(dep[x]-dep[y]));
x=fa[x][dis];
}
之后呢?应该怎么跳?
假设我们跳步跳到了一个相同的结点,那这个结点必定是它们公共祖先,但不一定最近;注意,不一定最近!
所以我们退而求其次,跳到最远的非公共祖先点,直到离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过程的时间复杂度为,故总时间复杂度为