【倍增】思想与操作

倍增

倍增是把时间复杂度为 O ( n ) 的一个操作变为 O ( log 2 n ) 的操作。
它与分治的思想类似,时间复杂度也类似。
它们的区别如下:
分治是将一个问题分成若干个子问题,最后的答案由子问题的答案合并得到。
倍增是将一个需要执行多次的操作分解,操作的结果直接由两个子操作的结果得到。

例子

最常见的例子就是在树上倍增。
每个询问要求节点 x 向上跳 s 次到达的节点编号。
若每次暴力向父亲节点跳,一共跳 s 次,时间复杂度特别大。
这时就要使用倍增。

如何实现

维护每个节点向上跳可到达的节点编号。
f [ i ] [ j ] 表示节点 i 向上跳 2 j 次到达的节点编号。
所有的 f [ i ] [ 0 ] 自然就为 f a t h e r [ i ] i 的父亲节点),其余的如何转移?
我们可以发现,向上跳 2 j 次就相当于是先向上跳 2 j 1 次,再向上跳 2 j 1 次。
于是我们不难得到转移方程:

f [ i ] [ j ] = f [ f [ i ] [ j 1 ] ] [ j 1 ]

void dfs(int i,int fa)
{
    f[i][0]=fa;
    for(j=1;j<=log(n);j++) f[i][j]=f[f[i][j-1]][j-1];
    for(j=last[i];j;j=next[j]) if(a[j]!=fa) dfs(a[j],i);    
}

怎么跳?
类似十进制转二进制的方式,从大到小枚举 j ,如果 s 2 j 就把 x 跳到 f [ x ] [ j ] ,并且把 s 2 j

for(j=log(n);j>=0;j--)
{
    if(s>=p[j]) 
    {
        x=f[x][j];
        s-=p[j];
    }
}

总结

可以用倍增实现的还有很多,如树上最大值、树上两点的最近公共祖先(LCA)等,对解题有很大帮助。
相信你已经对倍增了解不少了。要时刻记得,算法是死的而人是活的,必须学会灵活变通,找到题目突破口,才能顺利解题!

猜你喜欢

转载自blog.csdn.net/qq_39565901/article/details/81842002