倍增算法入门,简单明了的模拟+讲解

79 阅读3分钟

前言

倍增算法,顾名思义,就是不断地翻倍

虽然是一种基础算法,但它能够使得线性的处理转化为对数级的处理,大大地优化时间复杂度,在很多算法中都有应用,其中最常见的就是ST表以及LCA(树上最近公共祖先)了。

为了能够深刻理解这个算法,下面将通过两个十分简单的引例来导入倍增算法的说明。

引例一

在你面前的桌子上,摆着无数个重量为任意整数的胡萝卜;

接着告诉你一个数字 n ,问你要怎么挑选,使得你选出的胡萝卜能够表示出 [1,n] 区间内的所有整数重量?

读完题后我们马上就能想到一种选法,那就是选 n 个重量为1的胡萝卜,这样就能通过加减表示出 [1,n] 内的所有重量了。

但问题是……这样挑选的胡萝卜是不是太多了点?

我们很快就能发现,只需要选择重量为 1,2,4,8,16 的胡萝卜,就能表示 [1,31] 内的所有重量……只需要选择重量 1,2,4...2i2^i 的胡萝卜,就能表示 [1,2i+112^{i+1} -1] 内的所有重量。

也就是说, 对于给定的数字 n ,根本不需要选那么多胡萝卜,只需要 [log2nlog2n] ([]为向下取整)个胡萝卜就够啦!

由此引例我们得出一个结论:只需要 log2nlog2n 的预处理,就能表示出 [1,n] 区间内的所有情况。

引例二

有一个环状的操场,操场被分割为 [1,n] 个小块,每个小块上写着一个数字。

有一只小白兔站在操场的起点,它每次可以跳 k 个小块,然后拿走等同于它所站小块上数字数量的胡萝卜,问它跳 m 次,总共可以拿到几个胡萝卜?

如果能够算出来的话,小白兔就能把所有的胡萝卜都带回家吃啦!

注: 1kn106,1m10181≤k≤n≤10^6,1≤m≤10^18

同样的,读完题我们马上就能想到最简单暴力的方法:那就是让小白兔一次一次跳,每次跳都把答案加上去,直到跳完 m 次。

可是… m 最大可以达到 101810^18,小白兔就算跳到累死,恐怕也吃不到一根胡萝卜。

这时候我们想起了从引例一得出的结论:

只需要记录跳 1,2,4,8,16...2^[^l^o^g^2^m^] 次分别能够拿到的胡萝卜数,就能得到跳 [1,m] 区间内任何一个数字能拿到的胡萝卜数。

这样子,即便 m=1018m=10^18 ,也只需要预处理64以内的数据就可以了。

时间复杂度从 O(m)O(m) 变成了 O(nlog2m)O(nlog2m) (因为每个小块都需要处理),询问的时间复杂度则是 O(log2m)O(log2m) ,因为需要遍历其二进制的每一位。

接下来介绍处理方案:

我们设 to[x][i]to[x][i] 代表从起点 x 跳 2i2^i 步后到达的小块编号, carrot[x][i]carrot[x][i] 表示从起点 x 跳 2i2^i 步后能拿到的胡萝卜总数。

则有式子:

to[x][i]=to[to[x][i1]][i1]to[x][i]=to[to[x][i-1]][i-1]。即跳 2i2^i 步相当于先跳 2^i^-^1 步再跳 2^i^-^1 步。 carrot[x][i]=carrot[x][i1]+carror[to[x][i1]][i1]carrot[x][i]=carrot[x][i-1] + carror[to[x][i-1]][i-1]

写成代码如下:

为了好处理,我们设区间为左闭右开,即不计入终点的胡萝卜数。

for(int x=1;x<=n;x++) to[x][0]=(x+k)%n+1,carrot[x][0]=num[x];
for(int i=1;i<=64;i++)
    for(int x=1;x<=n;x++)
    {
        to[x][i]=to[to[x][i-1]][i-1];
        carrot[x][i]=carrot[x][i-1]+carrot[to[x][i-1]][i-1];
    }
int p=0,now=1,ans=0;
while(m)
{//若m的二进制第p-1位为1,则答案加上去
    if(m&(1<<p)) ans+=carrot[now][p],now=to[now][p];
    m^=(1<<p);//第p-1位清空
    p++;
}

就这样,小白兔高高兴兴地吃到了胡萝卜,皆大欢喜,皆大欢喜。

写在最后

可以说,倍增算法的核心式子就是 to[x][i]=to[to[x][i1]][i1]to[x][i]=to[to[x][i-1]][i-1]

即:对于 2i2^i 的处理,我们总可以通过 2i1+2i12^{i-1} + 2^{i-1} 来得到,而非 2i1+12^i-1+1 得到。

前者是 O(log2n)O(log_2n) 的时间复杂度,而后者则是 O(n)O(n)

最后,欢迎大家扫码关注下面公众号,回复“C100”可获得一份神秘技术资料!

640.jpg