之前暑假写过矩阵快速幂的题,但是很遗憾,自己当时并没有弄明白这个快速幂具体事怎么实现的,现在的话,自己就想把有关快速幂的模板做一个整理,也是对自己学过的知识的总结吧。
首先的话 快速幂,想到的肯定是二进制快速幂
之前看过一个博客上面写的我感觉总结的很到位:
快速幂就是 增大底数,减小指数,从而减小乘法的次数,达到快速的目的;
接下来就是简书上面的解析啦
快速幂的原理:
求
a
b
a^b
ab, 我们会发现,其实指数b是可以拆成二进制的。通过式子
a
m
+
n
=
a
m
∗
a
n
a^{m+n} = {a^m*a^n}
am+n=am∗an,我们可以发现,一旦指数b拆成二进制,那么a^b也可以进行相应的拆分。
例如,当b=11时,b的二进制为1011,即 11 = 1 ∗ 2 0 + 1 ∗ 2 1 + 1 ∗ 2 3 11=1*{2^0}+1*{2^1}+1*{2^3} 11=1∗20+1∗21+1∗23。
那么, a 11 = a 1 ∗ 2 0 + 1 ∗ 2 1 + 1 ∗ 2 3 = a 1 ∗ 2 0 ∗ a 1 ∗ 2 1 ∗ a 1 ∗ 2 3 = a 1 ∗ a 2 ∗ a 8 a^{11}=a^{1*{2^0}+1*{2^1}+1*{2^3}}=a^{1*2^0}*a^{1*2^1}*a^{1*2^3}=a^1*a^2*a^8 a11=a1∗20+1∗21+1∗23=a1∗20∗a1∗21∗a1∗23=a1∗a2∗a8。
经过上述操作,要求 a 11 a^{11} a11,我们只需要进行3次计算,而用朴素算法我们需要进行11次计算。
接着就是如何判断一个数在二进制形式的某个位置是0还是1,以及具体的代码实现了。
因为是二进制,我们很容易联想到位运算,这里我们需要用到与运算&和右移运算>>。
&运算:通常用于二进制取位操作,例如一个数x & 1 的结果就是取二进制的最末位的值。
还可以判断这个数的奇偶性,如果x&1==0,则x为偶数;如果x&1==1,则x为奇数。
在二进制中,就只有0和1这两个数,判断奇偶 很方便的判断0和1,
1的话就说明这一位是需要计算的。
个人觉得很巧妙
>>运算:在这里是作为除法来使用,例如一个数x,x >> 1就表示x右移一位,即x=x/2。
二进制快速幂:
long long Pow2(long long base,long long n,long long MOD){
/*base为底数,n为指数,MOD是要模的数*/
long long ans=1;
while(n){//n!=0,n>0
if(n&1)
ans = ans * base % MOD;
base = base*base % MOD;
n >>= 1;
// 其实就是 n/=2 的意思;用位运算简便一些吧;
}
return ans;
}
二进制矩阵快速幂:
/*由于c++ 中没有返回一个数组类型的东东,
所以这个时候就需要来借助结构体来实现返回 矩阵的操作了;*/
struct node {
int m[N][N];
};
long long MOD ;
node cul(node a,node b){//矩阵的乘法;
node ans;
for(int i=0;i<N;i++){
for(int j=0;j<N;j++){
ans.m[i][j] = 0;
for(int k=0;k<N;k++){
ans.m[i][j] += a.m[i][k]*b.m[k][j]%MOD;
}
}
}
return ans;
}
node Pow2(node base,long long n){
/*base为底数,n为指数,MOD是要模的数*/
node ans;
for(int i=0;i<N;i++){//把ans初始化为单位矩阵E;
for(int j=0;j<N;j++){
if(i == j)
ans.m[i][j]==1;
else
ans.m[i][j]=0;
}
}
while(n){
if(n&1)
ans = cul(ans,base);
// ans = ans * base % MOD;
//base = base*base % MOD;
base = cul(base,base);
n >>= 1;
// 其实就是 n/=2 的意思;用位运算效率更高;
}
return ans;
}
接下来
十进制快速幂:
十进制快速幂和二进制没有什么本质的区别,一个是十进制拆分指数,另一个是二进制拆分指数。
例如3
3
405
3^{405}
3405可以拆分为
(
3
1
)
5
∗
(
3
10
)
0
∗
(
3
100
)
4
(3^1)^5 * ( 3^{10} )^0 * (3^{100})^4
(31)5∗(310)0∗(3100)4.
//十进制快速幂
long long Pow10(long long base,long long MOD){
long long ans=1,base1;
while(n){
int k=n%10;
base1 = base;
if(k){
for(int i=0;i<k;i++)
base = base*base % MOD;
}
for(int i=1;i<=10;i++)
// 10进制倍增。 当然这个可以用二进制倍增来优化的
base = base * base1 %MOD;
n/=10;
}
return ans;
}
当指数很大的时候,就是long long(10^19) 都存不下的时候,就需要用字符串来进行存储了;
这个地方的话解决了我之前不理解 就是long long表示不了那个数,但是long long 这个长的字符串就可以存储是什么意思 ,现在弄明白了;
举个简单的例子吧,就10和长度为10的字符串,长度为10 的字符串可以存储10^10大小的数,
也就是说19个长度的字符串就可以存储long long型的数了 。
ps:长度为10的字符串表示的最大10位数,一个表示位数一个表示大小。
long long Pow10(long long base,string n,long long MOD){
long long ans=1,base1,len;
len = n.size()-1;
while(len){
int k=(n[len] - '0')%10;
base1 = base;
if(k){
for(int i=0;i<k;i++)
base = base*base % MOD;
}
for(int i=1;i<=10;i++)// 10进制倍增。
base = base * base1 %MOD;
len--
}
return ans;
}
十进制矩阵快速幂:
这个其实和十进制快速幂是一样的原理的,只不过是乘的单位从数值变成了矩阵而已。
struct node {
int m[N][N];
};
long long MOD ;
node cul(node a,node b){//矩阵的乘法;
node ans;
for(int i=0;i<N;i++){
for(int j=0;j<N;j++){
ans.m[i][j] = 0;
for(int k=0;k<N;k++){
ans.m[i][j] += a.m[i][k]*b.m[k][j]%MOD;
}
}
}
return ans;
}
long long Pow10(node base,string n){
/*base为底数,n为指数,MOD是要模的数*/
node ans,tem;
long long len=n.size()-1;
for(int i=0;i<N;i++){//把ans初始化为单位矩阵E;
for(int j=0;j<N;j++){
if(i == j)
ans.m[i][j]==1;
else
ans.m[i][j]=0;
}
}
while(len){
temp = base;
int k = (n[len]-'0') %10;
if(k)
ans = cul(base,ans);
for(int i=0;i<10;i++)
base = cul(base,temp);
len--;
}
return ans;
}