快速幂

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\times2^{3} + 1\times2^{2} + 0\times2^{1} + 1\times2^{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}\times3^{4}\times3^{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} \times 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\n", qmi(a, k, p));
    }
    return 0;
}
上一篇:PHP Ajax 跨域问题最佳解决方案【转】


下一篇:大牛自我总结500页“Java成长笔记”