快速幂

快速幂

1. 快速幂原理

快速幂

  • 快速幂需要解决的问题是:对于给定正整数a、b、p( 1 ≤ a 、 b 、 p ≤ 1 0 9 1 \le a、b、p \le 10^9 1abp109,p是任意正整数,不一定是质数),让我们求解 a b   m o d   p a^b \ mod \ p ab mod p的值。
  • 核心思想是将b看成一个二进制的数。具体求解分为两步:
  • (1)首先我们可以预处理出如下值:

a 2 0   m o d   p a 2 1   m o d   p . . . a 2 ⌊ l o g ( k ) ⌋   m o d   p a^{2^0} \ mod \ p \\ a^{2^1} \ mod \ p \\ ... \\ a^{2^{\lfloor log(k) \rfloor}} \ mod \ p a20 mod pa21 mod p...a2log(k) mod p

预处理的方式为:首先第一项是a,第二项式 a 2 a^2 a2,…,可以发现,后一项是前一项的平方。

  • (2)然后我们根据b的二进制表示以及上面对应的结果进行计算。假设 b = 2 x 1 + 2 x 2 + . . . 2 x t b=2^{x_1}+2^{x_2}+...2^{x_t} b=2x1+2x2+...2xt,则:

a b   m o d   p = a 2 x 1 + 2 x 2 + . . . 2 x t   m o d   p = ( a 2 x 1   m o d   p ) × ( a 2 x 2   m o d   p ) × . . . × ( a 2 x k   m o d   p ) a^b \ mod \ p = a ^ {2^{x_1}+2^{x_2}+...2^{x_t}} \ mod \ p \\ = (a ^ {2^{x_1}} \ mod \ p) \times (a ^ {2^{x_2}} \ mod \ p) \times ... \times (a ^ {2^{x_k}} \ mod \ p) ab mod p=a2x1+2x2+...2xt mod p=(a2x1 mod p)×(a2x2 mod p)×...×(a2xk mod p)

  • 上面两步的计算都是 O ( l o g ( n ) ) O(log(n)) O(log(n))的,因此次算法的时间复杂度是 O ( l o g ( n ) ) O(log(n)) O(log(n))的。
  • 具体代码实现时,上面的二个步骤可以同时计算,代码如下:
typedef long long LL;

// 返回 a ^ b mod p
LL qmi(int a, int b, int p) {
    
    LL res = 1 % p;  // 处理b=0, p=1的情况需要%p
    while (b) {
       if (b & 1) res = res * a % p;
       a = (LL)a * a % p;
       b >>= 1;
    }
    return res;
}

快速幂求逆元

  • 首先说明一下为什么需要逆元,思想是:将除法转为乘法运算。因为我们认为除法运算太难算了。

  • 逆元的定义:若整数 b,m 互质,并且对于任意的整数 a(这里的a也需要和m互质,否则后面的推导存在问题,但是不互质等式也能成立),如果满足 b|a,则存在一个整数 x,使得 a b ≡ a × x   ( m o d   m ) \frac{a}{b} \equiv a \times x \ (mod \ m) baa×x (mod m),则称 xb 的模 m 乘法逆元,记为 b − 1   ( m o d   m ) b^{-1} \ (mod \ m) b1 (mod m)

  • 由定义可知,只有b能整除aa也需要和m互质时,b才存在逆元。关于am互质的问题,可以参考如下评论:

在这里插入图片描述

  • b 存在乘法逆元的充要条件是 b 与模数 m 互质。当模数 m 为质数时, b m − 2 b^{m-2} bm2 即为 b 的乘法逆元。证明如下:

a b ≡ a × x   ( m o d   m ) \frac{a}{b} \equiv a \times x \ (mod \ m) baa×x (mod m)

即:
a b ≡ a × b − 1   ( m o d   m ) \frac{a}{b} \equiv a \times b^{-1} \ (mod \ m) baa×b1 (mod m)
等式的两边同时乘以b,得到:
a ≡ a × b − 1 × b   ( m o d   m ) a \equiv a \times b^{-1} \times b \ (mod \ m) aa×b1×b (mod m)
根据同余的性质,因为am互质,因此可以两边同时除以a,得到:
1 ≡ b − 1 × b   ( m o d   m ) b − 1 × b ≡ 1   ( m o d   m ) 1 \equiv b^{-1} \times b \ (mod \ m) \\ b^{-1} \times b \equiv 1 \ (mod \ m) 1b1×b (mod m)b1×b1 (mod m)
由条件知,m是质数,由费马小定理可知: b m − 1 ≡ 1 ( m o d   m ) b^{m-1} \equiv 1(mod \ m) bm11(mod m)

因此:b的逆元为 b m − 2 b^{m-2} bm2,即 b − 1 = b m − 2 b^{-1}=b^{m-2} b1=bm2

  • 举个例子,假设b=3, m=5,则b的范围在0~m-1之间的逆元为 b m − 2   m o d   m = 3 3   m o d   5 = 2 b^{m-2} \ mod \ m = 3^3 \ mod \ 5 = 2 bm2 mod m=33 mod 5=2,取a=6,则有

6 3 ≡ 6 × 2   ( m o d   5 ) \frac{6}{3} \equiv 6 \times 2 \ (mod \ 5) 366×2 (mod 5)

2. AcWing上的快速幂题目

AcWing 875. 快速幂

问题描述

分析

  • 使用快速幂求解即可。

代码

  • C++
#include <iostream>

using namespace std;

typedef long long LL;

LL qmi(int a, int b, int p) {
    
    LL res = 1 % p;  // 处理b=0, p=1的情况需要%p
    while (b) {
       if (b & 1) res = res * a % p;
       a = (LL)a * a % p;
       b >>= 1;
    }
    return res;
}

int main() {
    
    int n;
    scanf("%d", &n);
    
    while (n--) {
        int a, b, p;
        scanf("%d%d%d", &a, &b, &p);
        printf("%lld\n", qmi(a, b, p));
    }
    
    return 0;
}

AcWing 876. 快速幂求逆元

问题描述

分析

  • 根据上面的原理分析可知: a − 1 × a ≡ 1   ( m o d   p ) a^{-1} \times a \equiv 1 \ (mod \ p) a1×a1 (mod p),我们要求出a的逆元,根据定理知: a − 1 = a p − 2 a^{-1}=a^{p-2} a1=ap2

  • ap不互质时,无解;即ap的倍数时无解。

  • 因为要返回0~p-1之间的逆元,因此最终返回 a p − 2   m o d   p a^{p-2} \ mod \ p ap2 mod p

代码

  • C++
#include <iostream>

using namespace std;

typedef long long LL;

LL qmi(int a, int b, int p) {
    
    LL res = 1 % p;
    while (b) {
        if (b & 1) res = res * a % p;
        a = (LL)a * a % p;
        b >>= 1;
    }
    return res;
}

int main() {
    
    int n;
    scanf("%d", &n);
    
    while (n--) {
        int a, p;
        scanf("%d%d", &a, &p);
        
        if (a % p) printf("%lld\n", qmi(a, p - 2, p));
        else puts("impossible");  // 说明a是p的倍数
    }
    
    return 0;
}

AcWing 1289. 序列的第k个数

问题描述

分析

  • 等差数列:如果首项为 a 1 a_1 a1,公差为d,则 a n = a 1 + ( n − 1 ) × d a_n = a_1 + (n - 1) \times d an=a1+(n1)×d

  • 等比数列:如果首项为 a 1 a_1 a1,公比为q,则 a n = a 1 × q n − 1 a_n = a_1 \times q ^ {n - 1} an=a1×qn1

  • 输入的序列是否可能既是等差数列,又是等比数列呢?这种可能性是存在的,当且仅当输入的前三项全部相等时,才可能既是等差数列,又是等比数列。证明如下:假设前三项是a、b、c,则因为既是等差数列,又是等比数列,有:

{ 2 × b = a + c ( 1 ) b 2 = a × c ( 2 ) \begin{cases} 2 \times b = a + c \quad (1) \\ b ^ 2 = a \times c \quad (2) \end{cases} {2×b=a+c(1)b2=a×c(2)

将(1)式中的b值带入到(2)式,化简可以得到: ( a − c ) 2 = 0 (a - c) ^ 2 = 0 (ac)2=0,因此a == c,所以三项都是相等的。

  • 因此既是等差数列,又是等比数列,最后第k项计算出来的结果是一样的。

  • 另外如果这个序列是等比数列,其公比为 q = b a = b ′ a ′ , ( a ′ , b ′ ) = 1 q = \frac{b}{a} = \frac{b'}{a'}, (a', b')=1 q=ab=ab,(a,b)=1,即q的最简分数形式为 b ′ a ′ \frac{b'}{a'} ab,则

a n = a 1 × q n − 1 = a 1 × b ′   n − 1 a ′   n − 1 a_n = a_1 \times q ^ {n - 1} = \frac{a_1 \times b ^ {' \ n - 1}}{a ^ {' \ n - 1}} an=a1×qn1=a n1a1×b n1

因为n可以任意大,且 a n a_n an是整数,所以a'必定为1,因此公比q必定是整数,可以使用快速幂求解 a n a_n an

代码

  • C++
#include <iostream>

using namespace std;

typedef long long LL;

const int mod = 200907;

int qmi(int a, int k) {
    
    int res = 1 % mod;
    while (k) {
        if (k & 1) res = (LL)res * a % mod;
        a = (LL)a * a % mod;
        k >>= 1;
    }
    return res;
}

int main() {
    
    int n;
    cin >> n;
    while (n--) {
        int a, b, c, k;
        cin >> a >> b >> c >> k;
        
        if (b * 2 == a + c) cout << (a + (b - a) * (LL)(k - 1)) % mod << endl;
        else cout << (a * qmi(b / a, k - 1)) % mod << endl;
    }
    
    return 0;
}

AcWing 1290. 越狱

问题描述

分析

  • n个犯人,m中宗教信仰,问给每个人分配一个宗教信仰,使得存在相邻的人宗教信仰相同,有多少种方案数?

  • 我们可以求一下一共有多少种分配方式,然后减去不符合题目要求的分配方案,就是结果。

  • 一共的分配方案数,因为每个人都可以被分配m中宗教信仰中的一个,因此总方案数为: m n m^n mn

  • 不符合题目要求的方案数,即不存在相邻的人宗教信仰相同,则第一个人可以分配m中宗教信仰中的一个,后面的人均可以分配m-1中宗教信仰中的一个(只需要不和前一个相同即可),因此不符合题目要求的方案数为: m × ( m − 1 ) n − 1 m \times (m - 1) ^ {n - 1} m×(m1)n1

  • 因此分配方案数为: m n − m × ( m − 1 ) n − 1 m^n - m \times (m - 1) ^ {n - 1} mnm×(m1)n1

代码

  • C++
#include <iostream>

using namespace std;

typedef long long LL;

const int mod = 100003;

int qmi(int a, LL k) {
    
    int res = 1 % mod;
    while (k) {
        if (k & 1) res = (LL)res * a % mod;
        a = (LL)a * a % mod;
        k >>= 1;
    }
    return res;
}

int main() {
    
    LL n;  // n个人
    int m;  // m中宗教信仰
    cin >> m >> n;
    
    cout << (qmi(m, n) - (LL)m * qmi(m - 1, n - 1) % mod + mod) % mod << endl;
    
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值