1、快速幂是用来做什么的?
(1)快速求出 (a^k) 的结果! 比如 (2^{100}) 的结果 。简单粗暴快速幂
(2)快速求出 (a^k) (mod) (p) 的结果! 比如 (2^{100} \% 7) 的结果 。 常见快速幂
2、快速幂算法的原理
通过将指数拆分成几个因数相乘的形式,来简化幂运算
。在我们计算(3^{13}) 的时候,普通的幂运算算法需要计算(13)次,但是如果我们将它拆分成(3^{8+4+1}) ,再进一步拆分成 只需要计算(4)次。嗯?哪来的(4)次?,别急,接着看。
这种拆分思想其实就是借鉴了二进制与十进制转换的算法思想,我们知道(13)的二进制是(1101),可以知道:
(13=1 imes2^{3} + 1 imes2^{2} + 0 imes2^{1} + 1 imes2^{0} = 8 + 4 + 1)
原理就是利用位运算里的位移“>>”和按位与“&”运算,代码中 (k & 1)其实就是取(k)的二进制最低位,用来判断最低位是(0)还是(1),再根据是(0)还是(1)决定乘不乘,不理解的话联系一下二进制转换的过程。(k >>= 1)其实就是将(k)的二进制向右移动一位,就这样位移、取最低位、位移、取最低位,这样循环计算,直到指数(k)为(0)为止,整个过程和我们手动将二进制转换成十进制是非常相似的。
普通幂算法是需要循环指数次,也就是指数是多少就要循环计算多少次,而快速幂因为利用了位移运算,只需要算“指数二进制位的位数”次,对于(13)来说,二进制是(1101),有(4)位,就只需要计算(4)次,快速幂算法时间复杂度是(O(logn))级别,对于普通幂需要计算一百万次的来说,快速幂只需要计算(6)次,这是速度上质的飞跃,但是需要多注意溢出的问题。
3、举个栗子
把(3^{13})进行转化:
(3^{13}=3^{8+4+1}=3^{8} imes3^{4} imes3^{1})
如果我们能求出(3^{1}),(3^{4}),(3^{8}),我们就可以求出最后的(3^{13}),因为把它们乘在一起就可以了嘛。那怎么求呢?
(3^{1})可以求,因为就是3嘛,其它的呢?其它的有哪些需要我们提前准备好呢?
有: (3^{1}),(3^{2}),(3^{4}),(3^{8}) ,注意,只有这几个啊,其它的(3^{3}),(3^{5}),(3^{6}),(3^{7})可没有啊,不需要啊,因为有上面的这几个,就可以完美的组装出13了!
这些东东怎么求啊?这就简单了,就是每次翻一倍就是下一个了,比如 (3^{4}) = $3^{2} imes 3^{2} $
先来一个原始版本的快速幂,不考虑什么溢出之类,简单粗暴的来一拔:
4、简单粗暴快速幂(原始版本)
此方法是我用来方便理解的,不可用于实际工作中,或者说,没有实用价值。因为一来系统中有现成的(pow(n,m)),另一个是不防溢出基本在数论题中无用。
#include <iostream>
using namespace std;
typedef long long LL; //十年OI一场空,不开LONG LONG见祖宗
int qmi(int a, int k) {
int res = 1;
while (k) {
if (k & 1) res = res * a;
k >>= 1;
a = (LL) a * a;
}
return res;
}
int main() {
cout<<qmi(3,4)<<endl;
return 0;
}
5、简单粗暴快速幂+高精度乘法版本
这个就有点纠结了,因为什么呢?一般我们使用高精度,都是高精度乘以低精度,通常这样就够用了。但在这里不行,为啥呢?因为这里面是需要记录两个东西:
(1)结果res
。
(2)上一轮的结果a
,本轮把它平方做为本轮的底。
这两个家伙可都不是善茬,都可能是非常大的数字,都可能爆(long long),所以,都需要把它们两个放入高精度中,看来高精度乘法的两个模板(高精乘低精,高精乘高精)都是有必要背诵的。
下面是高精度乘以高精度的模板
#include <bits/stdc++.h>
using namespace std;
/**
* 功能:高精度乘高精度模板
* @param A
* @param b
* @return
*/
vector<int> mul(vector<int> &A, vector<int> &B) {
//初始化大小
vector<int> C(A.size() + B.size());
//先放里再说
for (int i = 0; i < A.size(); i++)
for (int j = 0; j < B.size(); j++)
C[i + j] += A[i] * B[j];
//处理余数
for (int i = 0, t = 0; i < C.size(); i++) {
t += C[i];
if (i >= C.size()) C.push_back(t % 10);
else C[i] = t % 10;
t /= 10;
}
//去掉前导0
while (C.size() > 1 && C.back() == 0) C.pop_back();
return C;
}
int main() {
string a, b;
cin >> a >> b;
//准备动作
vector<int> A, B;
for (int i = a.size() - 1; i >= 0; i--) A.push_back(a[i] - '0');
for (int i = b.size() - 1; i >= 0; i--) B.push_back(b[i] - '0');
//计算
vector<int> C = mul(A, B);
//倒序输出
for (int i = C.size() - 1; i >= 0; i--) cout << C[i];
return 0;
}
下面是高精度+快速幂的模板
//快速幂+高精度 a^b
vector<int> qmi(int a, int b) {
vector<int> ans; //结果数组
ans.push_back(1); //底
vector<int> A; //上一个依赖项
A.push_back(a);
while (b) {
if (b & 1) ans = mul(ans, A);
b >>= 1;
A = mul(A, A);
}
return ans;
}
这里需要特别指出一个练习题:https://www.luogu.com.cn/problem/P1045,洛谷的高精度+快速幂的练习题,还需要对高精度模板再进化,时刻保持(500)位!
时刻保留500位的代码如下:
//高精度乘法模板(高精乘高精)
vector<int> mul(vector<int> &A, vector<int> &B) {
//只保留500个长度
if(A.size()>500) A.resize(500);
if(B.size()>500) B.resize(500);
int la = A.size(), lb = B.size();
vector<int> C(la + lb + 10, 0);//提前申请结果所需的空间
for (int i = 0; i < la; i++)
for (int j = 0; j < lb; j++)
C[i + j] += A[i] * B[j];
for (int i = 0; i < C.size(); i++)
if (C[i] >= 10) {
C[i + 1] += C[i] / 10;
C[i] %= 10;
}
//处理前导0
while (C.size() > 1 && C.back() == 0)C.pop_back();
//只保留500个长度
if(C.size()>500) C.resize(500);
return C;
}
6、不断模防止溢出的快速幂
#include <iostream>
using namespace std;
//防止和爆INT
typedef long long LL;
int n;
// 快速幂 (a^k)%p
int qmi(int a, int k, int p) {
int res = 1; //答案
while (k) { //一路让k变小直到为0停止
if (k & 1) res = (LL) res * a % p; //如果k的个位是1的话
k >>= 1; //右移一位
a = (LL) a * a % p; //1-2-4-8-16,就是每进一位,是把a=a*a
}
return res;
}
//快速幂
int main() {
scanf("%d", &n);
while (n--) {
int a, k, p;
scanf("%d%d%d", &a, &k, &p);
printf("%d
", qmi(a, k, p));
}
return 0;
}