CC++ & Algorithm

同余式与模逆元

困难3
语言版本:C++
概述:同余式就像“星期几”的规律,模逆元则是找数字的“倒数”在模世界里的样子。

数学进阶中的数论与组合魔法:从同余到高斯消元

大家好!在编程学习的路上,我们常常会遇到一些有趣的数学问题。比如,你知道今天星期三,那么100天后是星期几吗?其实只要看100除以7的余数(100 mod 7 = 2),星期三加两天就是星期五。这种“只看余数”的玩法就是同余的核心思想。掌握同余以及相关的数论知识,不但能轻松解决生活中的周期问题,还能在密码学、组合计数、算法优化中发挥巨大作用。这篇文章将带你从同余出发,一路走过模逆元、扩展欧几里得、中国剩余定理、欧拉定理、容斥原理、卡特兰数,最后到达矩阵快速幂和高斯消元。每个知识点都会用生活例子帮你理解,用C++代码教会你实现。


1. 同余式与模逆元:星期几的数学秘密

什么是同余?

同余就像“除以某个数后只看余数”。如果两个数a和b除以m的余数相同,我们就说 a ≡ b (mod m)。比如:

  • 今天星期三,再过10天,10 mod 7 = 3,所以相当于星期三+3天=星期六。用同余式写就是 10 ≡ 3 (mod 7)。
  • 你的零花钱有15元,朋友有8元,你想知道两人钱数模5是不是一样?15 mod 5 = 0,8 mod 5 = 3,不一样。

生活中: 同余可以用来算周期。比如学校每隔7天放一次假,今天放假,那么第19天后放假吗?19 mod 7 = 5,所以不会,要再过5天才放假。

什么是模逆元?

在普通算术里,一个数a的倒数(乘法逆元)是满足 a × x = 1 的数x。在模的世界里,我们想找这样一个数x,使得 a × x ≡ 1 (mod m)。这个x就叫 a模m的逆元,记作 a⁻¹ mod m。

例子: 在模7的世界里,2的逆元是4,因为2×4=8≡1 (mod 7)。就像数字的“倒数”一样,有了逆元,我们就可以做“模除法”:计算 (5 ÷ 2) mod 7,就是5乘以2的逆元4,得到20 mod 7 = 6。

怎么找逆元?

最简单的方法是暴力尝试,从1到m-1一个一个试,看哪个数乘a后模m等于1。但这种方法很慢(尤其m很大时)。C++里可以用扩展欧几里得算法快速求出(后面会学到)。

下面是用暴力法求逆元的代码(适合m较小且保证逆元存在的情况):

#include <iostream>
using namespace std;

// 求 a 在模 m 下的逆元(暴力法,m较小且保证存在)
int modInverse(int a, int m) {
    for (int x = 1; x < m; x++) {          // x从1试到m-1
        if ((a * x) % m == 1)              // 如果乘起来模m等于1
            return x;                      // x就是逆元
    }
    return -1; // 不存在逆元
}

int main() {
    int a = 2, m = 7;
    cout << "2 的模 7 逆元是: " << modInverse(a, m) << endl; // 输出4
    return 0;
}

常见错误与注意

  • 逆元存在的条件: a和m必须互质(最大公约数为1)。如果不互质,逆元不存在。例如2在模4下就没有逆元,因为2和4不互质(gcd=2),找遍0~3都没有数使得2×x≡1 mod 4。
  • 暴力法只适合小模数: 当m很大(比如10⁹)时,循环m次会超时,必须用扩展欧几里得。
  • 结果可能是负数? 逆元通常取0到m-1之间的正整数,如果扩展欧几里得返回了负数,要加m调整为正。

2. 扩展欧几里得算法:水壶倒水的智慧

它用来干什么?

欧几里得算法(辗转相除法)可以快速求两个数的最大公约数gcd(a,b)。但有时候我们不仅想知道gcd,还想解方程 ax + by = gcd(a,b) 的整数解(x,y)。这就是扩展欧几里得算法的本领。

生活例子: 你有两个水壶,容量分别为a升和b升,你想倒出恰好等于它们最大公约数升的水。每次可以装满、倒空或互相倒水。扩展欧几里得就能告诉你最少需要多少次操作,以及每次怎么倒(实际上它给出了x和y,代表a的倍数加上b的倍数等于gcd)。

算法核心

递归求解:已知 gcd(b, a % b) 可以求出它们的解,然后反推回原方程。公式推导如下:

  • 递归基:当 b=0 时,gcd(a,0)=a,此时方程变为 a·x + 0·y = a,显然 x=1, y=0。
  • 递归过程:设 g = exgcd(b, a % b, x, y) 返回了gcd,并得到了方程 b·x + (a%b)·y = g 的一组解。注意 a%b = a - (a/b)*b,代入整理可得 a·y + b·(x - (a/b)*y) = g。所以新的x' = y, y' = x - (a/b)*y。

代码实现如下:

#include <iostream>
using namespace std;

// 扩展欧几里得,返回gcd,并设置x,y使 ax + by = gcd
int exgcd(int a, int b, int &x, int &y) {
    if (b == 0) {            // 递归结束
        x = 1;
        y = 0;
        return a;
    }
    int g = exgcd(b, a % b, x, y);  // 递归求下一层
    int temp = x;                   // 保存原来的x
    x = y;                          // 新x = 旧y
    y = temp - (a / b) * y;         // 新y = 旧x - (a/b)*旧y
    return g;
}

int main() {
    int x, y;
    int g = exgcd(18, 12, x, y);
    cout << "gcd = " << g << ", x = " << x << ", y = " << y << endl; // 得 gcd=6, x=1, y=-1
    // 验证:18*1 + 12*(-1) = 6
    return 0;
}

求模逆元

还记得之前要解 a * x ≡ 1 (mod m) 吗?这等价于 a·x + m·y = 1(因为a·x - 1是m的倍数,设为 m·(-y))。用exgcd(a, m, x, y)求出x后,调整x为正数就是a的逆元。例如求2模7的逆元:exgcd(2,7,x,y)得到x=-3,y=1,因为-3 mod 7 = 4,所以逆元是4。

// 用 exgcd 求逆元(更快)
int modInverseExgcd(int a, int m) {
    int x, y;
    int g = exgcd(a, m, x, y);
    if (g != 1) return -1;      // 不互质,无逆元
    return (x % m + m) % m;     // 调整为正数
}

常见错误

  • 忘记调整x为0~m-1:exgcd可能返回负数,须加上模数再取模。
  • 调用时参数顺序:exgcd(a, m, x, y) 求解 a·x + m·y = gcd(a,m),注意是a和m,不要搞反。
  • 当gcd不为1时逆元不存在,要检查返回值。

3. 中国剩余定理(CRT):多个闹钟同时响的时刻

故事背景

《孙子算经》有个经典问题:“一个数除以3余2,除以5余3,除以7余2,问这个数最小是多少?”答案23。中国剩余定理(CRT)就是解决这类“多个模条件”的通用方法。

生活例子: 你有很多个闹钟,闹钟1每3小时响一次,第一次在2小时后;闹钟2每5小时一次,第一次在3小时后;闹钟3每7小时一次,第一次在2小时后。下次所有闹钟同时响起是什么时候?CRT告诉你最早时间是23小时后。

算法步骤

  1. 计算总模数M:把所有模数乘起来,M = m1 × m2 × ... × mk。
  2. 对每个条件i
    • 计算 Mi = M / mi(去掉当前模数)。
    • 求 Mi 在模 mi 下的逆元 ti(即解 Mi·ti ≡ 1 (mod mi))。
  3. 组合答案:答案 ans = sum( ai × Mi × ti ) mod M,并取正数。

代码实现(用之前写的exgcd求逆元)

#include <iostream>
using namespace std;

// 扩展欧几里得求逆元(省略,直接用之前函数)
int exgcd(int a, int b, int &x, int &y) {
    if (b == 0) { x = 1; y = 0; return a; }
    int g = exgcd(b, a % b, x, y);
    int temp = x;
    x = y;
    y = temp - (a / b) * y;
    return g;
}

// 求逆元(模m,a与m互质)
int inv(int a, int m) {
    int x, y;
    exgcd(a, m, x, y);
    return (x % m + m) % m;
}

// 中国剩余定理,返回最小非负整数解
int crt(int n, int a[], int m[]) {
    int M = 1, ans = 0;
    for (int i = 0; i < n; i++) M *= m[i];            // 总模数
    for (int i = 0; i < n; i++) {
        int Mi = M / m[i];                            // 去掉当前模数
        int ti = inv(Mi % m[i], m[i]);                // Mi模mi的逆元
        ans = (ans + (long long)a[i] * Mi % M * ti) % M;
    }
    return ans;
}

int main() {
    int a[] = {2, 3, 2}; // 余数
    int m[] = {3, 5, 7}; // 模数
    cout << "答案: " << crt(3, a, m) << endl; // 输出23
    return 0;
}

常见错误与注意

  • 模数必须两两互质:如果模数不互质,上面的简单CRT不成立(结果可能错误)。需要更复杂的合并方法(例如用扩展欧几里得合并两个同余式)。
  • 答案需要取模:最后ans是M的剩余类,取模后就是最小非负解。
  • 注意整数溢出:a[i]*Mi可能很大,先用long long计算再取模。

4. 欧拉函数与欧拉定理:数出“朋友”个数,简化大幂运算

欧拉函数 φ(n) 是什么?

欧拉函数 φ(n) 表示1到n中与n互质的整数个数。例如 φ(6) = 2,因为1和5与6互质(2、3、4与6不互质)。怎么算呢?先对n分解质因数:n = p1^e1 × p2^e2 × ...,则 φ(n) = n × (1-1/p1) × (1-1/p2) × ...。

生活例子: 班级里有n个同学,其中有多少人和你(编号为某个数)没有共同的班级编号因子?比如n=6,你想找到和你“互质”的同学,只有2个人。

欧拉定理

如果a和n互质,那么 a^φ(n) ≡ 1 (mod n)。这个定理超级强大!比如计算 7^100 mod 10,因为φ(10)=4,7和10互质,所以7^4 ≡ 1 (mod 10),那么7^100 = (7^4)^25 ≡ 1^25 = 1。不用真的算7的100次方。

代码实现欧拉函数(试除法)

#include <iostream>
using namespace std;

int phi(int n) {
    int res = n;                          // 初始化为n
    for (int i = 2; i * i <= n; i++) {    // 试除到√n
        if (n % i == 0) {                 // i是质因子
            res = res / i * (i - 1);      // 应用公式 n*(1-1/i)
            while (n % i == 0) n /= i;    // 除掉所有i因子
        }
    }
    if (n > 1) res = res / n * (n - 1);   // 如果还有大质因子
    return res;
}

int main() {
    cout << "phi(6) = " << phi(6) << endl;  // 2
    cout << "phi(10) = " << phi(10) << endl; // 4
    return 0;
}

用欧拉定理做快速幂

假设我们要计算 a^b mod n,其中a与n互质,可以用 b = q·φ(n) + r 来简化:a^b ≡ a^r (mod n)。不过更通用的方法还是快速幂。

常见错误

  • 忘记检查互质性:欧拉定理要求a和n互质,如果gcd(a,n)≠1,不能直接用。
  • φ(n)计算要完整:分解质因数时,要确保把所有因子都除掉,最后剩余的>1也要处理。

5. 费马小定理与威尔逊定理:质数的魔法性质

费马小定理

如果p是质数,且a不是p的倍数(即gcd(a,p)=1),那么 a^(p-1) ≡ 1 (mod p)。这是欧拉定理的特例,因为对于质数p,φ(p)=p-1。

应用1:快速计算大幂模p
例如计算 2^100 mod 7。因为7是质数,2^6 ≡ 1 (mod 7),所以2^100 = 2^(6×16+4) = (2^6)^16 × 2^4 ≡ 1^16 × 16 mod 7 ≡ 2 (mod 7)。

应用2:质数判定(不靠谱)
如果对于某个a,a^(p-1) ≡ 1 mod p不成立,则p一定不是质数。但反过来不成立(有一些合数欺骗性很强,称为伪素数),比如2^340 ≡ 1 mod 341(341=11×31),所以费马小定理不能完全用来判定质数,但可以快速筛掉大多数合数。

威尔逊定理

p是质数当且仅当 (p-1)! ≡ -1 (mod p)。例如p=5,(4!)=24 ≡ 4 ≡ -1 (mod 5)。这个定理理论意义大,但直接算阶乘太大,不能用于实际判质。

代码:快速幂验证费马小定理

#include <iostream>
using namespace std;

// 快速幂计算 a^b mod p
long long quickPow(long long a, long long b, long long p) {
    long long res = 1;
    while (b) {
        if (b & 1) res = res * a % p;   // 如果最低位是1
        a = a * a % p;                  // a自乘
        b >>= 1;                        // 右移
    }
    return res;
}

int main() {
    long long p = 7;
    cout << "2^100 mod 7 = " << quickPow(2, 100, p) << endl; // 2
    // 验证费马小定理:2^6 mod 7 = 1
    cout << "2^6 mod 7 = " << quickPow(2, 6, p) << endl; // 1
    return 0;
}

常见错误

  • 误用费马小定理:p必须是质数且a不被p整除。如果p不是质数,结果不一定成立。
  • 快速幂乘法溢出:两个long long相乘可能超过范围,用 (a*a)%p 已经用模运算保证了,但中间乘积在C++中可能先溢出,建议用 128位或写乘法取模函数(如快速乘)。

6. 容斥原理:数人数时去掉重复的

基本思想

容斥原理用来计算多个集合并集的元素个数。最简单的公式:|A∪B| = |A| + |B| - |A∩B|。比如班里学数学的有10人,学语文的有8人,两科都学有3人,那么总人数是10+8-3=15。

对于三个集合,公式是: |A∪B∪C| = |A|+|B|+|C| - |A∩B| - |A∩C| - |B∩C| + |A∩B∩C|。

一般地,奇数个交集相加,偶数个交集相减。

生活例子

求1到100中能被2、3或5整除的数的个数。直接数很麻烦,用容斥:

  • 能被2整除:100/2=50个
  • 能被3整除:33个
  • 能被5整除:20个
  • 能被2和3(即6)整除:16个
  • 能被2和5(即10)整除:10个
  • 能被3和5(即15)整除:6个
  • 能被2、3、5(即30)整除:3个 总数 = 50+33+20 -16-10-6 +3 = 74。

代码实现:枚举子集(二进制)

#include <iostream>
using namespace std;

int gcd(int a, int b) { return b ? gcd(b, a % b) : a; }
int lcm(int a, int b) { return a / gcd(a, b) * b; }

int main() {
    int n = 100;
    int primes[] = {2, 3, 5};
    int k = 3;
    int cnt = 0;
    // 枚举所有非空子集(用二进制位表示)
    for (int mask = 1; mask < (1<<k); mask++) {
        int l = 1, bits = 0;
        for (int i = 0; i < k; i++) {
            if (mask & (1<<i)) {
                l = lcm(l, primes[i]);   // 计算子集元素的最小公倍数
                bits++;
            }
        }
        int num = n / l;                  // 能被这个最小公倍数整除的个数
        if (bits % 2 == 1) cnt += num;    // 奇数个元素加
        else cnt -= num;                  // 偶数个元素减
    }
    cout << "1~100中能被2,3,5之一整除的数有: " << cnt << "个" << endl; // 74
    return 0;
}

常见错误

  • 最小公倍数可能溢出:当数字较大时,l = lcm(l, prime) 可能超过int范围,应使用long long或适时判断。
  • 重复减加顺序:注意符号,奇数加偶数减。
  • 忘记包含空子集:mask从1开始,不包括空集。

7. 卡特兰数:排队不插队的计数规则

它是什么?

卡特兰数是组合数学里一系列整数,第n项记为C_n。它有着各种各样的几何和组合意义。最著名的定义是:n对括号的合法匹配数。例如n=3时,有5种:
((())) (()()) (())() ()(()) ()()()

其他等价问题:栈的出栈序列数、n个节点能形成的二叉树个数、凸多边形三角形划分方法数等。

生活例子: 你有一串票,要按顺序进游乐场,但排队时想用栈来中转,有多少种不同的排队顺序?也是卡特兰数。

计算公式

  • 递推式:C0=1, Cn = (2*(2n-1)/(n+1)) * C(n-1)
  • 封闭公式:Cn = (1/(n+1)) * C(2n, n)

代码实现(递推求前10项)

#include <iostream>
using namespace std;

int main() {
    long long catalan[20] = {1};               // C0=1
    for (int i = 1; i <= 10; i++) {
        catalan[i] = catalan[i-1] * 2 * (2*i - 1) / (i + 1);
        cout << "C(" << i << ") = " << catalan[i] << endl;
    }
    return 0;
}

常见错误

  • 整数除法的顺序:递推式中除法必须保证整数。先乘后除,由于分子包含 (i+1) 的因子,结果一定是整数。
  • 数字增长很快:n=20时卡特兰数约6.56e9,已经超过int,要用long long或高精度。

8. 矩阵快速幂:飞快地算递推数列

背景

普通快速幂可以快速计算 a^b。如果把a换成矩阵,就能在O(logn)时间内计算矩阵的n次幂。这在解递推数列时特别有用。例如斐波那契数列F0=0, F1=1, Fn=Fn-1+Fn-2。可以写成矩阵乘法:

[ Fn ] = [1 1] ^ (n-1) * [F1] [ Fn-1 ] [1 0] [F0]

只要计算出矩阵的 (n-1) 次幂,再乘以初始向量,就能得到Fn。矩阵乘法有结合律,所以可以用快速幂。

代码实现(求斐波那契第n项模1e9+7)

#include <iostream>
#include <vector>
using namespace std;

typedef vector<vector<long long>> Matrix;   // 矩阵类型
const long long MOD = 1e9 + 7;

// 矩阵乘法
Matrix mul(Matrix a, Matrix b) {
    int n = a.size();
    Matrix c(n, vector<long long>(n, 0));
    for (int i = 0; i < n; i++)
        for (int k = 0; k < n; k++)           // 优化:交换k和j循环可提高缓存友好性
            for (int j = 0; j < n; j++)
                c[i][j] = (c[i][j] + a[i][k] * b[k][j]) % MOD;
    return c;
}

// 矩阵快速幂
Matrix matrixPow(Matrix a, long long p) {
    int n = a.size();
    Matrix res(n, vector<long long>(n, 0));
    for (int i = 0; i < n; i++) res[i][i] = 1; // 单位矩阵
    while (p) {
        if (p & 1) res = mul(res, a);
        a = mul(a, a);
        p >>= 1;
    }
    return res;
}

// 求斐波那契第n项(F0=0, F1=1)
long long fib(long long n) {
    if (n == 0) return 0;
    Matrix base = {{1, 1}, {1, 0}};
    Matrix m = matrixPow(base, n - 1);
    return m[0][0]; // F_n
}

int main() {
    cout << "F(10) = " << fib(10) << endl; // 55
    return 0;
}

常见错误

  • 矩阵乘法维度:必须是方阵,且大小相同。
  • 快速幂的循环:指数p要反复除以2,直到0。
  • 初始化单位矩阵:对角线为1,其他地方0。
  • 乘法取模:每个加法后取模,避免溢出。

9. 高斯消元法:解方程组的系统方法

它是什么?

解方程组时,我们常常通过“消去未知数”来逐步化简。高斯消元法用矩阵(增广矩阵)的形式,通过行变换(交换行、倍乘行、一行加若干倍另一行)将矩阵化为行阶梯形,然后回代求解。

生活例子: 你有两种铅笔,一次买1支A和2支B花了5元,一次买3支A和4支B花了11元。问A和B各多少钱?解方程组: 1·x + 2·y = 5 3·x + 4·y = 11
高斯消元能帮你找到x=1, y=2。

算法步骤

  1. 建立增广矩阵:把系数和常数写在一起。
  2. 对于第col列(从0开始),在当前行row开始找该列绝对值最大的元素(选主元),交换到当前行。
  3. 用主元行消去下面所有行的第col列(把下面的该列变为0)。
  4. 重复下一列,直到处理完所有列或行。
  5. 回代:从最后一行开始,逐行求解每个未知数。

代码实现(浮点数)

#include <iostream>
#include <vector>
#include <cmath>
using namespace std;

const double EPS = 1e-9;   // 误差阈值

vector<double> gauss(vector<vector<double>> a) {
    int n = a.size();      // 方程个数
    for (int col = 0, row = 0; col < n && row < n; col++) {
        int sel = row;
        for (int i = row; i < n; i++)               // 选主元
            if (fabs(a[i][col]) > fabs(a[sel][col]))
                sel = i;
        if (fabs(a[sel][col]) < EPS) continue;      // 该列全为0,跳过
        swap(a[row], a[sel]);                       // 交换到当前行
        for (int i = row + 1; i < n; i++) {         // 消去下面行的该列
            double f = a[i][col] / a[row][col];
            for (int j = col; j <= n; j++)
                a[i][j] -= f * a[row][j];
        }
        row++;
    }
    // 回代
    vector<double> x(n, 0);
    for (int i = n - 1; i >= 0; i--) {
        double sum = a[i][n];
        for (int j = i + 1; j < n; j++)
            sum -= a[i][j] * x[j];
        if (fabs(a[i][i]) < EPS) {                  // 无解或无穷多解
            // 这里简单返回空向量;实际需要额外处理
            return vector<double>();
        }
        x[i] = sum / a[i][i];
    }
    return x;
}

int main() {
    vector<vector<double>> a = {
        {1, 2, 5},
        {3, 4, 11}
    };
    vector<double> x = gauss(a);
    if (x.empty()) {
        cout << "无解或无穷多解" << endl;
    } else {
        cout << "x = " << x[0] << ", y = " << x[1] << endl; // x=1, y=2
    }
    return 0;
}

常见错误

  • 浮点精度问题:判断是否为0时用EPS,不要直接比较 ==0。
  • 主元选择:如果选到绝对值很小的数,会导致数值不稳定,所以通常取绝对值最大的。
  • 无解或无穷多解:当回代时某行系数全0但常数不为0,则无解;全0常数也为0则有无穷多解。实际程序中要判断。

完整综合示例:用数论知识解决“星期几”扩展问题

假设今天星期一,问经过 a 天后是星期几?如果 a 很大(比如10^100),直接计算很麻烦。我们可以利用费马小定理或欧拉定理简化。例如模7的世界里,10^100 mod 7 = ?因为7是质数,10 mod 7 = 3,3^6 ≡ 1 mod 7,所以3^100 = 3^(6*16+4) ≡ 3^4 = 81 ≡ 4 mod 7,所以从星期一加4天是星期五。这比直接算10^100快多了!

#include <iostream>
using namespace std;

long long quickPow(long long a, long long b, long long p) {
    long long res = 1;
    while (b) {
        if (b & 1) res = res * a % p;
        a = a * a % p;
        b >>= 1;
    }
    return res;
}

int main() {
    // 题目:今天星期一,问 10^100 天后是星期几?
    // 星期天为0, 星期一为1, ..., 星期六为6
    // 返回 (1 + 10^100) mod 7
    int today = 1; // 星期一
    long long base = 10, exp = 100, mod = 7;
    long long days = quickPow(base % mod, exp, mod);
    int result = (today + days) % mod;
    cout << "10^100 天后是星期" << result << "(0~6分别表示星期日~星期六)" << endl;
    return 0;
}

相关指引与延伸学习

  • 如果你想深入了解欧几里得算法,可以看 扩展欧几里得 的证明和应用(如解二元一次方程)。
  • 中国剩余定理在实际中有很多变种(如非互质模数的CRT),感兴趣可以搜索“CRT合并”。
  • 欧拉函数和欧拉定理是RSA加密的基础,联系数学与计算机安全。
  • 容斥原理在排列组合、概率计算中广泛应用。
  • 卡特兰数常出现在栈、二叉树、多边形划分等问题中,试试自己推导递推式。
  • 矩阵快速幂不仅可以解斐波那契,还可以用于图论中的路径计数(如求两点间长度恰好为k的路径数)。
  • 高斯消元是线性代数的基础,也是许多科学计算库的底层算法。

希望这篇文章能帮你打开数论与组合数学的大门,让你在编程中更自如地解决各种数学问题。记住,数学不是枯燥的数字游戏,而是理解世界规律的魔法钥匙!

例题精讲

1单选题

在模 26 的整数环中,下列哪个整数存在模 26 的乘法逆元?

A13
B15
C8
D2
2判断题

若 a ≡ b (mod m) 且 c ≡ d (mod m),则 a^c ≡ b^d (mod m) 恒成立。

3填空题
下面函数使用扩展欧几里得算法求整数 a 模 m 的乘法逆元(假设存在),请补全空缺处代码。

int mod_inverse(int a, int m) {
    int x, y;
    int g = extended_gcd(a, m, x, y);
    if (g != 1) return -1;
    return ___;
}
4单选题

已知模数 p 为素数,且 a 不是 p 的倍数。根据费马小定理,a 模 p 的乘法逆元可以表示为:

Aa^(p-1)
Ba^(p-2)
Ca^(p-1) - 1
D(p-1) * a
5判断题

对于所有正整数 n>1,若整数 a 满足 gcd(a, n) > 1,则 a 一定没有模 n 的乘法逆元。