数据结构与算法学习笔记----快速幂

数据结构与算法学习笔记----快速幂

@@ author: 明月清了个风

@@ first publish time: 2025.1.2

ps⭐️快速幂的两道模版题,快速幂,乘法逆元,费马小定理

Acwing 875. 快速幂

[原题链接](875. 快速幂 - AcWing题库)

给定 n n n组 a i , b i , p i a_i,b_i,p_i ai,bi,pi,对于每组数据,求出 a i b i    m o d    p i a_i^{b_i} \; mod \; p_i aibimodpi的值

输入格式

第一行包含整数 n n n,

接下来 n n n行,每行包含三个整数 a i , b i , p i a_i,b_i,p_i ai,bi,pi。

输出格式

对于每组数据,输出一个结果,表示 a i b i    m o d    p i a_i^{b_i} \; mod \; p_i aibimodpi的值。

每个结果占一行。

数据范围

1 ≤ n ≤ 100000 1 \le n \le 100000 1≤n≤100000,

1 ≤ a i , b i , p i ≤ 2 × 1 0 9 1 \le a_i,b_i,p_i \le 2\times 10^9 1≤ai,bi,pi≤2×109、

思路

快速幂是一种高效的计算整数幂的算法,适用于大数的幂运算,比如这题的数据范围, a i , b i , p i a_i,b_i,p_i ai,bi,pi都可以到亿的级别,通过快速幂可以将暴力运算的 O ( n ) O(n) O(n)时间复杂度优化到 O ( log ⁡ n ) O(\log n) O(logn)级别, n n n是幂的范围。

核心思想 :将指数 b b b通过二进制分解,从而达到 log ⁡ b \log b logb的运算量,也就是

a b    m o d    p = a ( 2 x 1 ) + 2 x 2 + ⋯ + 2 x k    m o d    p = a 2 x 1 a 2 x 2 ⋯ a 2 x k    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 ) \begin{align*} a^b \; mod \; p & = a^{(2^{x_1}) + 2^{x_2} + \cdots + 2^{x_k}} \; mod \; p \\ & = a^{2 ^ {x_1}} a^{2 ^ {x_2}} \cdots a^{2^{x_k}} \; mod \; p \\ & = (a^{2^{x_1}}mod \;p)\cdot(a^{2^{x_2}}mod \;p) \cdots(a^{2^{x_k}}mod \;p) \\ \end{align*} abmodp=a(2x1)+2x2+⋯+2xkmodp=a2x1a2x2⋯a2xkmodp=(a2x1modp)⋅(a2x2modp)⋯(a2xkmodp)

在代码中,我们也无需对 k k k的二进制分解及 a x a^x ax进行预处理,只需要边运算边处理就行,具体看下面代码吧,代码还是很清晰的。

代码

cpp 复制代码
#include <iostream>
#include <cstring>

using namespace std;

typedef long long LL;

int qmi(int a, int k, int p)
{
   int res = 1;
   
   while(k)   
   {
       if(k & 1) res = (LL) res * a % p;   // 从k的二进制表示最低位开始,也就是2的0次方,此时a的2的0次方就是a
       k >>= 1;  // 每次右移一位
       a = (LL) a * a % p;  // 每次将a平方,因为a上面是2的次方,每次提高一位相当于乘一个a的2的0次方,就是a
   }
   return res;
}

int main()
{
    int n;
    cin >> n;
    
    while(n --)
    {
        int a, b, p;
        cin >> a >> b >> p;
        cout << qmi(a, b, p) << endl;
    }
    
    return 0;
}

Acwing 876. 快速幂求逆元

[原题链接](876. 快速幂求逆元 - AcWing题库)

给定 n n n组 a i , p i a_i,p_i ai,pi,其中 p i p_i pi是质数,求 a i a_i ai模 p i p_i pi的乘法逆元,若逆元不存在则输出impossible

注意:请返回 0 ∼ p − 1 0 \sim p - 1 0∼p−1之间的逆元。

乘法逆元的定义

若整数 b , m b,m b,m互质,并且对于任意的整数 a a a,如果满足 b ∣ a b|a b∣a,则存在一个整数 x x x,使得 a b ≡ a × x ( m o d    m ) \frac{a}{b} \equiv a \times x (mod \; m) ba≡a×x(modm),则称 x x x为 b b b模 m m m的乘法逆元,记为 b − 1 ( m o d    m ) b^{-1}(mod \; m) b−1(modm)。

b b b存在乘法逆元的充要条件是 b b b与模数 m m m互质,当模数 m m m为质数时, b m − 2 b^{m - 2} bm−2即为 b b b的乘法逆元。

输入格式

第一行包含整数 n n n,

接下来 n n n行,每行包含一个数组 a i , p i a_i,p_i ai,pi,数据保证 p i p_i pi是质数。

输出格式

输出共 n n n行,每组数据输出一个结果,每个结果占一行。

若 a i a_i ai模 p i p_i pi的乘法逆元存在,则输出一个整数表示逆元,否则输出impossbile

数据范围

1 ≤ n ≤ 1 0 5 1 \le n \le 10^5 1≤n≤105,

1 ≤ a i , p i ≤ 2 ∗ 1 0 9 1 \le a_i, p_i \le 2 * 10^9 1≤ai,pi≤2∗109

思路

主要的难点是定义比较绕,我们可以对定义的式子进行一些变形

a b ≡ a × x ( m o d    m ) (1) \frac{a}{b} \equiv a \times x (mod \; m) \tag{1} ba≡a×x(modm)(1)

在式(1)的两边同乘 b b b,我们可以得到式(2)

a ≡ b × a × x ( m o d    m ) (2) a \equiv b \times a \times x (mod \; m) \tag{2} a≡b×a×x(modm)(2)

两边再同时除 a a a可以得到

1 ≡ b × x ( m o d    m ) (3) 1 \equiv b \times x (mod \; m) \tag{3} 1≡b×x(modm)(3)

因此我们要求的就是一个 x x x,可以使 b × x ≡ 1 ( m o d    m ) b \times x \equiv 1 (mod \; m) b×x≡1(modm)

还有一个注意点是 b b b存在乘法逆元的充要条件是模数 m m m与 b b b互质,题目中给出了条件模数 p i p_i pi保证了是质数,也就是保证了这个条件。

这里需要补充一个额外的定理:费马小定理

当 m m m是一个质数,对于任意整数 a a a( a a a不被 m m m整除),有 a m − 1 ≡ 1 ( m o d    m ) a^{m- 1} \equiv 1 (mod \; m) am−1≡1(modm)

那么对费马小定理的这个公式进行变形,拆分次方可得 a ⋅ a m − 2 ≡ 1    ( m o d    m ) a \cdot a^{m - 2} \equiv 1 \; (mod \; m) a⋅am−2≡1(modm),我们就能直接得到 a a a的乘法逆元是 a m − 2 a^{m - 2} am−2。

因此问题转换为求 a m − 2 a^{m - 2} am−2,也就是应用上面的快速幂。

需要注意的是题目虽然保证了 p p p是一个质数,但是却被没有保证我们的充要条件,也就是 a i a_i ai与 p i p_i pi互质,因此需要判断 a i a_i ai是否是 p i p_i pi的倍数,只有这样的情况他们才不互质。

代码

cpp 复制代码
#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>

using namespace std;

typedef long long LL;

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

int main()
{
    int n;
    cin >> n;
    
    while(n --)
    {
        int a, p;
        cin >> a >> p;
        if(a % p == 0) puts("impossible");
        else cout << qmi(a, p - 2, p) << endl;
    }
    
    return 0;
}
相关推荐
Laplaces Demon30 分钟前
Spring 源码学习(八)—— 对象工厂
学习
打工人你好38 分钟前
iOS 逆向学习 - iOS Architecture Media Layer
学习·ios
懒大王爱吃狼2 小时前
Python基于wordcloud库绘制词云图
开发语言·python·学习·python基础·python学习
1101 11012 小时前
STM32-笔记28-蓝牙模块
笔记
一弓虽2 小时前
java 基础学习——java 异常详细介绍
java·学习·throw
π大星星️3 小时前
haproxy+nginx负载均衡实验
linux·运维·服务器·笔记·nginx·负载均衡
半夏知半秋3 小时前
Python中的OS库
服务器·开发语言·笔记·后端·python·学习
yvestine4 小时前
数据挖掘——概论
人工智能·笔记·机器学习·数据挖掘
一只小萌新.4 小时前
【Python学习(六)——While、for、循环控制、指数爆炸】
开发语言·python·学习
王磊鑫4 小时前
计算机网络基础(7)中科大郑铨老师笔记
服务器·笔记·计算机网络