看了许多关于快速幂及快速幂取模的博客,今天分享一下自己对快速幂的理解,如果哪里写的不对,请在评论区留言,我会及时更改,谢谢。
先来看个题目
撞杆子的CC学姐
17级学姐中有个学姐,人称撞杆子的CC,因为CC学姐走路的时候总是在低头玩手机,所以经常撞杆子,在一次把鼻子撞破后,CC学姐在想自己出一次门究竟要撞多少个杆子。一天CC学姐打算去广场玩,已知CC学姐走完全程共撞n次杆子,CC学姐第一次撞杆子时是走了1米的时候,每次撞完杆子后潜意识都会改变自己的小心程度,即第i次撞杆子后经过2i % 107米后会撞下一个杆子,现在CC学姐想知道自己走了多远。
输入格式
输入一个n(1 <= n <= 10^5),CC学姐走完全程要撞的杆子数量
输出格式
输出一个s,CC学姐走完全程所走的距离
样例输入
1
3
4
样例输出
1
7
15
分析:这个题首先想到的就是暴力求解,然后手写个pow函数,然后跑一次循环。
int pow(int a,int b)
{
int ans=1;
int i;
for(i=1;i<=b;i++)
ans*=a;
return ans;
}
时间复杂度为O(n),很显然时间复杂度比较高,我们可以使用快速幂算法将其降到O(logn)。
快速幂
博主是采用位运算来解释,因为位运算比较好理解而且速度比较快。
举个例子:
23
对于指数3我们可以化成二进制的形式,也就是0011;
然后把二进制化成权值的表示形式 0* 23+0* 22+1* 21+1* 20;
换到原来的式子中变成
2^ ( 1* 2 1+1* 20)
即 2^ (1*21) * 2^ (1 * 20)
也就是说我们可以根据二进制的权值来进行计算。对于位的部分我们依次取,取到是0便累乘,是1就把累乘的答案乘到答案中,所以可以得到如下代码:
int quick(int x,int n)//x为底数,n为指数
{
int sum=1;//最小2^0是1
while(n)
{
if(n&1)
{
sum*=x;
}
x*=x;
n>>=1;
}
return sum;
}
解释一下这里的n&1。
1化成二进制后只有最后一位是1,其余的都是0,与n进行异或便可得到n最后一位的值。例如上面的23和1异或得到
0011 & 0001
然后n每次右移一位,便可取出n的所有位。
快速幂取模
懂了上面的快速幂之后,理解快速幂取模也就容易很多。
先来看一下数论里面的一个定理:
(a*b)%c=((a%c) *(b%c))%c;
由这个式子可推出另外一个定理:
(a^ b)%c=(a%c) ^b%c;
快速幂取模主要应用的是第二条推理,我们可以不断的对底数进行取模,缩小底数范围,然后再对整个快速幂进行取模,就可以得到快速幂取模的操作,具体代码如下。
int quick(int x,int n,int m)//x为底数,n为指数,m为模
{
int sum=1;
while(n)
{
if(n&1)
{
sum=(sum*x)%m;
}
x=(x*x)%m;
n>>=1;
}
return sum;
}
以上是快速幂和快速幂取模的核心代码,对于给出的例题完整代码如下
#include<stdio.h>
int quick(int x,int n,int m)
{
int sum=1;
while(n)
{
if(n&1)
{
sum=(sum*x)%m;
}
x=(x*x)%m;
n>>=1;
}
return sum;
}
int main()
{
int n;
while(scanf("%d",&n)!=EOF)
{
int i;
long long int sum=0;
for(i=0;i<n;i++)
{
sum+=quick(2,i,107);
}
printf("%lld\n",sum);
}
}