所谓倍增算法,顾名思义,是以2的乘方加倍增加的算法。 加倍算法的运用有很多种,这里重点说明加倍的基本使用方法、RMQ解法的ST算法以及求出树中两点的最近的共同祖先LCA。
基本用法加倍的主要目的是在单调的数据集中找到某个数字。
例如,在数组a{2、5、7、11和19}中查找最大的小于12的数字。
朴素的做法:从第一个数开始,一个个列举出来,然后再找。
二分法:每次将数列分成一半进行判断,再寻找子区间。
倍增方法:设定生长长度p和确定的长度l,确定现在a[l p]是否满足条件,如果满足条件(小于12 ),p就会加倍生长; 否则进行p筛选(尝试筛选判断条件) ) ) )。
主要代码:
l=0; p=1; 如果可以扩展while(p )//范围) l ),则(if ) a[lp]12 ) ) l=p; //增加已知范围p=1//倍增,p*=2 } else { p=1; //缩小范围}}couta[l]; 倍频算法一般稳定,时间o(logn )
乘方有名的快速乘方也与加倍一线有关。 快速乘方主要以二进制分解形式创建,二进制比特权则以加倍思想创建。
二进制分解二和一个数字可以分解为二进制加法的形式。 例如13=(1101 )2=(10001001 )2=8) 41
乘方ab=AC*a(B-c )常识
即,a13=a(1101 )2=a8 * a4 * a1
ab=a(b/2 ) a (b/2 ) if b%2==0
即,a8=a4 * a4
要计算s的a次方,假设p=ak。 其中,如果k是二进制的当前位数,则其中k可以加倍增加。 k每次增加1,相当于比特权乘以2。 这样,p*=p,显然是合法的。
如果a的第k位是1,也就是说,可以将a分为2k,则ans*=p。
组合代码实际上可以省略k变量。 每次a去掉最后一位就可以了。
初始化p=s。
longlongpow(ints,int a ) { long long p=s,ans=1; while(a ) ) if ) a1 ) ans*=p; p*=p; a1; }返回Ans; }求解rmq问题的著名ST算法是成倍增长的产物,ST算法主要用于解决多次询问区间最高值rmq的问题。
【问题2】给出n个,有m次询问,每次输出区间[x,y]的最大值。
如果f[i][j]与DP一起表示距离I的长度为2j的区间的最大值,则f[i][j]=max(f[i][j-1],f[I2[j-1]][j-1]的方程式会稍微出现这里,f[i][j-1]表示区间[i,I2(j-1 )-1]的最大值,f[I2(j-1 ) ]表示区间[I2 ) j-1 ],i 2j]的最大值。
现在处理咨询。 从x,y开始,[x,x 2k-1]区间的最大值为max(f[x][k],f [ y-2k ] ),使得[x,x 2k-1]和[y-2k 1,y]正好复盖[x,y]的整个区间
ST算法预处理(求f数组)
for(intI=1; i=n; I ) f(I ) )0)=a(I ); //一个数的区间本身为for(intj=1; (1j )=n; j ) for(intI=1; I(1j )-1=n; I ) f(I ) ) j )=max ) f ) I ) ]][j-1],f ) I ) 1j ) [j-1]; ST算法的查询过程
for(intI=1; i=m; I ) { cinxy; intk=log2(y-x1 ); coutmax(f[x][k],f[y-(1k )1][k]endl; }总算法的复杂度为o(O(nlognm ) )预处理o (nlogn ); 每次o(1)、m次o ) )
求解LCA问题的最近的共同祖先共同祖先:两个节点x、y共有的祖先节点
最近共同祖先(LCA )两个节点x、y共享的最深的祖先节点
【问题3】如果给出由n个节点构成的树,则有m次询问,每次都会输出节点x、y最近的共同祖先号。
先预处理每个节点的深度depth
再次结合DP思想,假设f[i][j]表示节点I向上移动2j后的祖先节点的编号
f[i][j]=f[f[i][j-1]][j-1]
还是这样理解,把2j步分成一半,走到那一半的路程就是f[i][j-1],再走半分钟就是f[I][j-1][j-1]。 f [ I ] [0]=初始化father [ I ]。
在讯问过程中,首先试着用加倍法使x和y的深度depth相等。 假设深度[ x ]深度[ y ]
int p=1,s=x; while(p ) if ) Depth[s]p=Depth[y] ) s=f [ s ] [ log2 ] p ]; p=1; } else { p=1; }}x=s; 深度相等时,如果x=y,则直接输出y。 否则,再次用加倍法求出最远的非共同祖先的节点d,最后输出f[d][0]即可。
预处理时间复杂度:查询o(nlogn )、复杂度O(mlogn )。