快速幂
今天是我在福州集训的第六天,在今天,我学习了贪心和分治这两种思想。我当然收货了很多。但最令我受益的吧吧吧——就是快速幂了!要知道快速幂在2017年初赛中也是考到了!!也可见其的重要性!!
在学习快速幂之前,我们要知道一下问题:
Q:快速幂是什么?
A:快速幂是一种专门计算a^b%p的一种算法,对于这种操作,快速幂是最好的。当然我们还会在下面具体解释。
Q:虽然快速幂很好用,但我们也要知道他在什么地方用。如果连什么时候用都不知道~那么就算快速幂再厉害神仙也就不了你了呀~~
A:对于快速幂,我们一般在a^b%p中当b极大是使用。在b极大的时候普通的数是可以用暴力写的,但是复杂度却为O(b),当b极大是TLE的可能性还是很大的。如果高精度的话,作取模运算未免又太麻烦了。所以一些伟大的大佬们就创造出了一种神奇的算法——It means快速幂!他不仅代码实现容易,而且时间复杂度优秀,为log级的,即使很大的数也能用非常快的时间做掉。
对于快速幂,我们有两种实现的方法,一种是非递归算法,另一种就是递归算法(讲了跟没讲似的。),那么我们接下就会一一介绍。
两种方法的例题都是a^b%p,求结果。(我们的约定:b在long long范围内)(看到这数据有没有绝望~~用暴力也会超时吧!但是我们用快速幂就能非常非常轻松的通过。)
一、非递归方法
我们首先可以将b转化为一个二进制数,记x为当前二进制位下的值(这样讲可能会有点抽象,接下来会具体说),在另记一个数s为当前的值。算法就是当遍历到当前b二进制位为1时,就s=s*x%p(注,mod p千万不能忘,包括之前,为了保险可以再每部都mod 一个p。当然不用模的时候千万不能模,不然答案有可能是错误的。)
每次遍历一次就将x=x*x一次(这里不用判断,而且是不能判断当前位是否为1,因为x的值是跟当前的二进制位是没关系的)
接下来给一个例子:
例如:2^8%10
那么我们就按照刚刚的方法来模拟:
8二进制展开后为1 0 0 0
x初值为a,即x=2;
s初值为1,即s=1;
①开始时遍历到0,s不变,x=x*x%p=4;
②第二次遍历到0,s不变,x=x*x%p=6
③第三次遍历到0,s不变,x=x*x%p=6
④第四次遍历到1,s=s*x%x=6,x=x*x%p=6;
遍历完毕,结束。
因为这里涉及到幼儿园奥数中的同余问题,这里就不详细介绍了。
那么这时可能会有人怀疑最后的结果到底对不对??那么我们就用暴力算法对拍一下。
暴力算法为:2*2*2*2*2*2*2*2=256;
256 mod 10为6.
诶很惊喜!答案居然对了呢!
这就说明这方法是可行的。
那么接下来就是代码实现:
long longx=a,s=1;//初值(注意!这里必须要long long 当p巨大时int是会炸的)
while (b!=0)//b还没遍历完+-86*86-+
{
if (b%2==1)//若当前位是1,就进行累乘
s=s*x%p;
x*=x;//自身累乘
x%=p;//注意模
b/=2//其实就是模拟二进制算法,除以二,知道为0为止
}
emmmmmmmmmmm,这代码也太短了吧~~应该是可以理解的!
二、递归法(二分法)
对于这种操作,我们就要从二分的角度去想。这使我们想到了幂的一种性质:a^x+y=a^x*a^y;那么我们就很容易想到:我们可以将b拆分成b/2与b/2(对于偶数而言,对于奇数的话就在额外乘一个a)。
如果我们将这个二分函数称为f的话,就有以下公式:
b为偶数:f(a,b/2,p);
b为奇数:a*f(a,b/2,p);
那么有了公式后,代码实现就很容易了,代码如下:
int f(int a,int b,int p){
if (b==0)//已经到底
return 1%p;
else{
long long res=f(a,b/2,p);//注意,只能是longlong,具体原因前面已讲
res*=res;//已经求过一遍,不用求第二遍,平方。
res%=p;//取模
}
reurn res;
}
那么这就是快速幂的两种方法,应该是好理解的吧~