倍增 思想与操作

倍增

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

例子

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

如何实现

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

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 j j,如果 s ≥ 2 j s≥2^j s2j就把 x x x跳到 f [ x ] [ j ] f[x][j] f[x][j],并且把 s − 2 j s-2^j s2j

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

总结

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

posted @ 2018-08-19 21:46  AnAn_119  阅读(69)  评论(0编辑  收藏  举报