今天想介绍一个在中低难度算法竞赛中经常碰到的算法---快速幂,于此相匹配的还有一个叫矩阵快速幂的东西。不过这篇文章只介绍快速幂,之后有时间再更新矩阵快速幂的知识。
快速幂,顾名思义,就是快速求解某一个数的指数次方结果为多少。
普通求解
如让你求2^5次方,你可以很快计算出结果为2*2*2*2*2=32。
这种方式在计算机中可以使用pow函数解决,即pow(2,5)=32。引入头文件cmath就行了,当然你也可以手写。
pow函数的实现方式大致如下
typedef long long ll;
ll pow(ll a,ll b,ll c)
{
ll d=1;
while(b--){
d=d*a%c;
}
return d;
}
不难看出,这种计算方式的速度是由b决定的,即时间复杂度为O(b),而如果b很大的,那就超时了啊!
快速幂
下面就进入本节的主要内容,用快速幂求解,这其实是对二进制的巧妙使用。
讲一个例子,现在要求3^11,按上述方法需要计算11次,但快速幂可以将求3^11简化为(3^8)*(3^2)*(3^1),这样就只计算了3次,其实总共比较了4次,往下看就懂了。
来一波分析:
- 将指数11转化为二进制,即00...001011(32位,前面的0我就省略不写了,太多容易看乱)
- 从右到左开始,32位的01串我们在下面给它们一个数值标识,从3^1开始,以平方的方式不断递增,如下面的表格所示(注意只给出最后边的6位,int类型总共应该有32位)
0 | 0 | 1 | 0 | 1 | 1 |
3^32 | 3^16 | 3^8 | 3^4 | 3^2 | 3^1 |
其实下面的指数也可以有另外一种方式理解,即当前位置的1表示十进制的哪个数,指数就是哪个。
所以下面的操作就很简单了,3^11=3^(8+2+1),为什么要拆成这几个数呢,因为他们就是11转成二进制后,对应的1的位置数值的表示啊(上面的表格),而这些位置的1代表的数加起来就是11.
那么计算的思路就是,读取11的二进制数,若该位置为1,就乘上该位置所代表的数,若为0则不作处理。
时间复杂度从O(N)降到了O(logN)
具体方法:将11与1做按位与位运算,若结果为1,说明11二进制最后的数为1,所以乘上。
然后下一步我们让11做右移位运算,即将32个01集体往右移动1位,这时最左侧会补上0,而最右侧的1已经不见了。相当于继续比较下一位,若当前数值为0,即二进制32个都为0,说明已经没有1了,就可以结束循环。
具体的代码实现
#include <iostream>
using namespace std;
typedef long long ll;
/*ll pow(ll a,ll b,ll c)
{
ll d=1;
while(b--){
d=d*a%c;
}
return d;
}*/
ll quickPow(ll a,ll b,ll c)
{
ll d=1;
while(b){
if(b&1){
d=d*a%c;
}
a=a*a%c;//更新当前位置代表的数
b=b>>1;//右移1位
}
return d;
}
int main()
{
cout<<"10的1亿次方mod3的结果为: "<<quickPow(10,1e9,3);
return 0;
}
矩阵快速幂
typedef long long ll;
const int mod=1e9+7;
const int MaxN=105;//矩阵大小
struct Mat{
ll m[MaxN][MaxN];
}ans,a;//结果与输入矩阵
Mat Mul(Mat a,Mat b,int n){
Mat c;
memset(c.m,0,sizeof(c.m));
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
for(int k=1;k<=n;k++)
c.m[i][j]=(c.m[i][j]+(a.m[i][k]*b.m[k][j])%mod)%mod;
return c;
}
Mat _power(Mat a,ll b,int n){
for(int i=1;i<=n;i++)
ans.m[i][i]=1;
while(b){
if(b&1)
ans=Mul(ans,a,n);
a=Mul(a,a,n);
b>>=1;
}
return ans;
}