快速幂
先讨论无需取模的
当b为偶数时:ab=a(b/2)*2=(a2)b/2
当b为奇数时:ab=a*ab-1=a*(a2)(b-1)/2
如 28=(22)4 27=2*(22)3
所以,我们可以如此迭代下去
210=(22)5=(22)*[(22)2]2
① ② ③
指数为10 是一个偶数,则底数2平方,指数变为一半 [ ①→② ]
指数为5 是一个奇数,则先将底数提出作为系数(22),此时指数为4 是一个偶数,则底数22再平方,指数再变为一半 [ ②→③ ]
归纳总结得到:
当指数大于1时,若为 偶数 则将指数除以2,底数平方
若为 奇数 则先提出一个为底数的系数(可直接把该系数乘进ans中),所以指数减1,然后再按照 偶数 的办法做
不断迭代下去,当指数为1时,则直接得出答案
最后只要将每次相乘时取模即可,时间复杂度O(log2b)
1 inline int mi(int a,int b) 2 { 3 int ans=1; 4 a%=mo; 5 while (b) 6 { 7 if (b&1) ans=ans*a%mo; 8 b>>=1; 9 a=a*a%mo; 10 } 11 return ans; 12 }
- 对于任何一个整数的模幂运算
- a^b%c
- 对于b我们可以拆成二进制的形式
- b=b0+b1*2+b2*2^2+...+bn*2^n
- 这里我们的b0对应的是b二进制的第一位
- 那么我们的a^b运算就可以拆解成
- a^b0*a^b1*2*...*a^(bn*2^n)
- 对于b来说,二进制位不是0就是1,那么对于bx为0的项我们的计算结果是1就不用考虑了,我们真正想要的其实是b的非0二进制位
- 那么假设除去了b的0的二进制位之后我们得到的式子是
- a^(bx*2^x)*...*a(bn*2^n)
- 这里我们再应用我们一开始提到的公式,那么我们的a^b%c运算就可以转化为
- (a^(bx*2^x)%c)*...*(a^(bn*2^n)%c)
- 这样的话,我们就很接近快速幂的本质了
- (a^(bx*2^x)%c)*...*(a^(bn*2^n)%c)
- 我们会发现令
- A1=(a^(bx*2^x)%c)
- ...
- An=(a^(bn*2^n)%c)
- 这样的话,An始终是A(n-1)的平方倍(当然加进去了取模匀速那),依次递推
- int quick(int a,int b,int c)
- {
- int ans=1; //记录结果
- a=a%c; //预处理,使得a处于c的数据范围之下
- while(b!=0)
- {
- if(b&1) ans=(ans*a)%c; //如果b的二进制位不是0,那么我们的结果是要参与运算的
- b>>=1; //二进制的移位操作,相当于每次除以2,用二进制看,就是我们不断的遍历b的二进制位
- a=(a*a)%c; //不断的加倍
- }
- return ans;
- }
我们来大致的推演一下快速幂取模算法的时间复杂度
首先,我们会观察到,我们每次都是将b的规模缩小了2倍
那么很显然,原本的朴素的时间复杂度是O(n)
快速幂的时间复杂度就是O(logn)无限接近常熟的时间复杂度无疑逼朴素的时间复杂度优秀很多,在数据量越大的时候,者中优化效果越明显
POJ1995
题意:
快速幂版题
- #include"iostream"
- #include"cstdio"
- #include"cstring"
- #include"cstdlib"
- using namespace std;
- int ans=0;
- int a,b;
- int c;
- int quick(int a,int b,int c)
- {
- int ans=1;
- a=a%c;
- while(b!=0)
- {
- if(b&1) ans=(ans*a)%c;
- b>>=1;
- a=(a*a)%c;
- }
- return ans;
- }
- int main()
- {
- int for_;
- int t;
- scanf("%d",&t);
- while(t--)
- {
- ans=0;
- scanf("%d%d",&c,&for_);
- for(int i=1;i<=for_;i++)
- {
- scanf("%d%d",&a,&b);
- ans=(ans+quick(a,b,c))%c;
- }
- printf("%d\n",ans);
- }
- return 0;
- }