α. 引入理由
α1. 速度快
在一般情况下,加法的运算速度比乘法的运算速度快,位运算的速度比加法速度快,因此,将乘法转化为多次加法,把幂运算转化为多次乘法会提升运算速度.
α2. 溢出问题
一般用到快速乘,快速幂的数据都很大,直接相乘或相加会溢出,一般结果都需要对其取余
(a mod p) R (b mod p) = (a R b) mod p
上述为数论中的模运算公式,R指的是四则运算,作为快速乘,快速幂的理论支撑.
β. 快速乘(a*b)
要把乘分为加法,我们首先想到的是,使用一个for循环,将a进行b次相加,并在每次循环时,进行取模运算,得到结果.但这种方法看起来并没有很高的优化,因为时间复杂度为O(n).
而快速乘可以是时间复杂度减小为O(log(n))
一般思路 | 快速乘 |
---|---|
4*5 | 4*5 |
4+4+4+4+4 | (4+4)*2 + 4 |
20 | 20 |
我们可以对比出来快速乘进行了三次运算(乘法我们可以使用位运算进行优化),而一般思路进行了五次
#include <iostream>
#define ll long long
#define M 12345654
using namespace std;
ll quickMul(ll a, ll b){
ll res = 0;
while (b){
if(b&1){ // 如果 b 为奇数,res需要把一个a吸收掉,保证b为偶数
res = (res%M+a%M)%M;
}
a = (a % M + a % M)%M; // 将 a 变大 2倍
b>>=1; // 将 b 减小 2倍,上面 吸收的步骤保证b为偶数
}
return res;
}
int main(){
cout<<quickMul(123423,64556687)<<endl; // 5998887
return 0;
}
γ. 快速幂(a^b)
与快速乘相同,快速幂可以将时间复杂度减小到O(log(n))
思路和快速乘类似,只不过是将+换为*,所以快速幂里面的乘法也是可以使用快速乘来完成的哦~
#include <bits/stdc++.h>
#define M 998244353
#define ll long long
using namespace std;
ll quickM(ll a,ll n){
ll res = 1;
while (n){
if(n&1){ // 使用位与来判断奇偶
res = (res * a)%M;
}
a = (a * a)%M;
n>>=1; // 减小两倍使用位运算
}
return res;
}
int main(){
ios::sync_with_stdio(false);
printf("%lld",quickM(23467654,576546787)); // 42700162
return 0;
}
δ. O(1)快速乘
卧槽,这个牛 QAQ,快记下来!
LL quickMul(LL A,LL B,LL mod) // LL 指的是 long long
{
return (A*B-(LL)((long double)A*B/mod)*mod+mod)%mod;
}