树上倍增求lca

嗯~ o(* ̄▽ ̄*)o

lca是树上两点的最近公共祖先。如果在同一个分支上就是更靠近根的那个点,否则就是大家一起向上走,第一次能都经过的那个点。

根据这两个性质,我们对于每次询问可以把一个向上走到根节点,标记走过的点。然后从另一个点向上走,直到遇到第一个标记过的点即为lca。

如果整个树是一个长链,这样的方法就会退化成一复杂度为n的算法。我们想一想如何优化成为log的策略。

假设两个节点到达lca的距离是m,那么这个m一定是可以被分解成一个二进制数的(废话)。我们可以好好利用一下这个性质。

设数组fa[i][k]表示节点i向上走2^k的父节点,d[i]表示i节点的深度。那么可以得到fa[i][0]是从i向上走1个到达的节点,即父节点。而且还可以得到fa[i][k]=fa[fa[i][k-1]][k-1],因为二进制的定义。d[i]=d[fa[i][0]]+1应该不用再说了吧。

同时还可以维护许多数组表示其他性质,在topsort的时候需要自己加上。

//传入的参数即为你选中的根节点,实际上这个根节点选什么都一样
t=(int)log(n*1.0)/log(2.0)+1;//为最大的k
void bfs(int now)
{
    stack<int>q;
    q.push(now);d[now]=1;
    while(q.size())
    {
        int x=q.top();q.pop();
        for(int j=link[x];j!=0;j=o[j].next)
        {
            int y=o[j].y;
            if(d[y])continue;
            q.push(y);
            d[y]=d[x]+1;
            fa[y][0]=x;
            for(int k=1;k<=t;k++)
                fa[y][k]=fa[fa[y][k-1]][k-1];//类似于动态规划的转移呦
        }
    }
}

然后对于每个询问,先把x向上调整到于y同一深度,如果在同一个链上这个时候应该已经遇到了吧。如果没有遇到就可以俩人一起向上走,知道fa[x][k]==fa[y][k]的时候停下来。

int lca(int x,int y)
{
    if(d[x]>d[y])
        swap(x,y);
    for(int k=t;k>=0;k--)
        if(d[fa[y][k]]>=d[x])
            y=fa[y][k];
    if(x==y)return x;
    for(int k=t;k>=0;k--)
        if(fa[x][k]!=fa[y][k])
            x=fa[x][k],y=fa[y][k];
    return fa[x][0];
}

 

posted @ 2018-09-11 17:41  zzuqy  阅读(160)  评论(0编辑  收藏  举报