快速幂算法C++
一、前置知识
1.二进制表示数字
注意:每个十进制数都可以由唯一的二进制数表示.
EXAPMPLE:
13 = ( 1101 ) 2 = 1 × 2 0 + 0 × 2 1 + 1 × 2 2 + 1 × 2 3 13=(1101)_2=1\times2^0+0\times2^1+1\times2^2+1\times2^3 13=(1101)2=1×20+0×21+1×22+1×23
2.位运算
a.& 运算符
&运算符可以直接对二进制进行操作,1&1=1,1&0=0,0&1=0,0&0=0,因为奇数的二进制表示末尾数字都是1,所以 &可以直接用来判断奇偶性
#include <bits/stdc++.h>
using namespace std;
int main(){
int a;cin>>a;
if(a&1) cout<<"a是奇数"<<"\n";
else cout<<"a是偶数"<<"\n";
return 0;
}
b.“<<”左移 和 “>>”右移 运算符
在此处你只需要记住>>n代表除
2
n
2^n
2n,<<n代表乘
2
n
2^n
2n
EXAMPLE:
12
=
(
1100
)
2
>
>
1
−
−
−
(
110
)
2
=
6
12=(1100)_2 >>1 ---(110)_2=6
12=(1100)2>>1−−−(110)2=6
12
=
(
1100
)
2
<
<
1
−
−
−
(
11000
)
2
=
24
12=(1100)_2 <<1 ---(11000)_2=24
12=(1100)2<<1−−−(11000)2=24
3.分治思想
分治策略(Divide and Conquer)是一种算法设计策略,通过将一个问题分解成多个相同或类似的子问题,然后递归地解决这些子问题,并将它们的解合并起来得到原问题的解。分治策略通常包括三个步骤:分解(Divide)、解决(Conquer)、合并(Combine)。
- 分解(Divide):将原始问题划分为若干个规模较小、结构与原问题相似的子问题。这通常是通过递归地将问题划分成更小的子问题来完成的。
- 解决(Conquer):递归地解决各个子问题。当子问题的规模足够小时(可以直接求解),不再需要继续递归时,就称为“基本情况”。
- 合并(Combine):将子问题的解合并成原始问题的解。这一步通常在递归的过程中完成,而不是在最后进行。
分治策略的优势在于它可以将复杂的问题分解成相对简单的子问题,使得问题的求解更加清晰和高效。然而,分治策略并不适用于所有类型的问题,有时候子问题之间的合并成本可能很高,从而抵消了分解所带来的收益。
二、快速幂是什么?
简而言之,快速幂算法是用来快速计算指数表达式的值的一种算法,例如
2
1000000000
2^{1000000000}
21000000000,普通的计算方法
2
×
2
×
2
×
.
.
.
.
.
.
2\times2\times2\times......
2×2×2×......乘1000000000-1次,而计算机1s最多运行
5
×
1
0
8
5 \times10^8
5×108次,所以普通算法无法满足要求。而快速幂算法的时间复杂度为O(log n),其中n为指数的位数,所以只需要运算28.897353次,满足要求。相比于传统的逐次相乘的方法,时间复杂度更低,尤其是在指数很大的情况下,优化效果更加明显。
例:
2
13
2^{13}
213只需要计算3次即可得出结果,首先
13
/
2
=
6
13/2=6
13/2=6,
6
/
2
=
3
6/2=3
6/2=3,
3
/
2
=
1
3/2=1
3/2=1。
注意此处对时间复杂度的优化只考虑乘法的循环次数,右移操作和内部乘法操作都视为常数操作,忽略不计,原本优化之前乘法运算要循环n次,而优化之后只需要运算log n次,如1e12最后只需要40次,所以优化效果明显。
三、算法理解+代码实现
前置知识 ( a × b × c × d ) m o d p = ( ( ( a × b ) p × c ) m o d p × d ) m o d p (a \times b \times c \times d ) \mod p = (((a \times b) p \times c) \mod p \times d) \mod p (a×b×c×d)modp=(((a×b)p×c)modp×d)modp
1.经典理解方法
3 10 = ( 3 2 ) 5 = 9 5 = 9 × 9 4 = 9 × ( 81 ) 2 = 9 × ( 81 2 ) 1 3^{10}=(3^2)^5=9^5=9\times9^4=9\times(81)^2=9\times({81}^2)^1 310=(32)5=95=9×94=9×(81)2=9×(812)1
//此处以3^10为例进行解释说明
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int mod=998244353;
int quickrow(int a,int b){//注意a表示底数,b表示指数
int ans=1;//用来储存答案
while(b){//若指数不为零,就进行运算
if(b%2==1){//若指数为奇数,则提出一个底数,进行偶指数运算,如9^5变成9*9^4
ans=ans*a%mod;//将拿出来的底数乘到ans中
a=a*a%mod;//底数平方
b/=2;//指数除2
}
else {//偶数直接进行指数和底数操作
a=a*a%mod;
b/=2;
}
}
return ans%mod;//返回答案
}
signed main(){
int a,b;cin>>a>>b;
cout<<quickrow(a,b);
return 0;
}
2.二进制分解理解法(精简版)
2 13 = 2 ( 1101 ) = 2 8 × 2 4 × 2 1 2^{13}=2^{(1101)}=2^8\times2^4\times2^1 213=2(1101)=28×24×21
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int mod=998244353;
int quickrow(int a,int b){
int ans=1;
while(b){//将指数看成二进制形式
if(b&1) ans=ans*a%mod;//如果是1说明存在
a=a*a%mod;//a不管二进制位是0还是1,都不断的自乘
b>>=1;//右移一位
}
return ans%mod;
}
signed main(){
int a,b;cin>>a>>b;
cout<<quickrow(a,b);
return 0;
}
总结
此算法较为常用,建议记住,下一篇讲解利用快速幂求解逆元