Skip to content

裴蜀定理

对于任意整数 a,b,存在一对整数 x,y,满足 ax+by=gcd(a,b)

证明:

在欧几里得算法的最后一步,即 b=0 时,显然有一对整数 x=1,y=0 使得 a1+00=gcd(a,0)=a

b>0,则 gcd(a,b)=gcd(b,amodb)。假设存在一对整数 x,y 满足 bx+(amodb)y=gcd(b,amodb),因为 bx+(amodb)y=bx+(aba/b)y=ay+b(xa/by),所以令 x=y,y=xa/by,就得到了 ax+by=gcd(a,b)

由数学归纳法可知,裴蜀定理成立。

扩展欧几里得算法

由裴蜀定理的证明过程可得扩展欧几里得算法:

cpp
int exgcd(int a, int b, int &x, int &y) {
    if (b == 0) {
        x = 1;
        y = 0;
        return a;
    }
    int d = exgcd(b, a % b, x, y);
    int t = x;
    x = y;
    y = t - y * (a / b);
    return d;
}

扩展欧几里得算法可求出 ax+by=gcd(a,b) 的一组特解 (x,y)。,那么由原式可知通解的形式如下:

x=x+bgcd(a,b)ty=yagcd(a,b)t

其中 t 为任意整数。上式可理解为特解 x 加上一个数,而 y 减去一个数,得到的结果是不变的。

明确了通解的形式,那么如何求 x 的最小整数解呢?其实就是让 x 每次移动 b/gcd(a,b) 单位,直到到达临界点。该操作可以使用模运算快速完成。设 d=b/gcd(a,b),则 x 的最小整数解为 (x%d+d)%d

求解不定方程 ax+by=c

扩展欧几里得算法求解不定方程的过程如下:

  1. 判断方程 ax+by=c 是否有整数解。有解的充要条件是 gcd(a,b)c
  2. 使用扩展欧几里得算法求 ax+by=gcd(a,b) 的一组特解 (x,y)
  3. 在等式 ax+by=gcd(a,b) 的两边同时乘以 cgcd(a,b),得到 axcgcd(a,b)+bycgcd(a,b)=c
  4. 对照 ax+by=c 得到它的一个解为:x0=xcgcd(a,b)y0=ycgcd(a,b)
  5. d=gcd(a,b),则 ax+by=c 的通解可以表示为:x=x0+bdty=y0adt其中 t 为任意整数。

求解线性同余方程

给定整数 a,b,m 求解线性同余方程 axb(modm) 的整数解,或报告无解。

axb(modm) 等价于 m(axb),即 axmy=b,又因为 y 可以是任意整数,所以原方程等价于 ax+my=b,此时只需要求不定方程的解即可。类似地,当 gcd(a,m)b 时,方程 ax+my=b 有整数解,且恰有 gcd(a,m) 个模 m 不同余的解,分别是 xi=x0+i(m/gcd(a,m))(0igcd(a,m)1)

求逆元

若整数 bm 互质,则存在整数 x 满足 bx(modm),称 xb 的模 m 乘法逆元,记为 b1(modm)。也就是说在模意义下除以 b 就等于乘上 b 的逆元。因此,求 b 的逆元,等价于求同余方程 bx1(modm) 的整数解。

cpp
int inv(int b, int m) {
    int x, y;
    exgcd(b, m, x, y);
    return (x % m + m) % m;
}

上述代码求解逆元的时间复杂度为 O(logn)

费马小定理求逆元

m 是质数时,将 m 记作 p,若 b<p,根据费马小定理 bp11(modp),则 bbp21(modp),因此模 p 意义下 b 的逆元为 bp2

这里就可以使用快速幂求解 bp2,时间复杂度为 O(logp)

cpp
int inv(int b, int p) {
    return qmi(b, p - 2, p)
}

线性递推求逆元

当需要求 1n 每一个数的逆元时,可以采用线性递推的方法,时间复杂度为 O(n)

线性递推求解逆元的思路如下:

使用 inv[i] 表示 i 的逆元,当 i<pp 为质数)时,有递推式:inv[i] = (p - p / i) * inv[p % i] % p

证明:

i<p 时,有 p=ki+r,其中 k=p/i,r=p%i,有:

ki+r0(modp)

两边同时乘上 i 的逆元 i1,得:

kii1+ri10(modp)k+ri10(modp)

移项得:

ri1k(modp)

两边同时乘上 r 的逆元 r1,得:

r1i1rkr1(modp)i1kr1(modp)

代入 k=p/i,r=p%i,得:

i1(pi)(p%i)1(modp)i1(ppi)(p%i)1(modp)

具体代码如下:

cpp
int n, p, inv[N];

void init_inv(int n, int p) {
    inv[1] = 1;
    for (int i = 2; i <= n; i++) {
        inv[i] = (p - p / i) * inv[p % i] % p;
    }
}

中国剩余定理

《孙子算经》中记载了这样一个问题:有物不知其数,三三数之剩二,五五数之剩三,七七数之剩二,问物几何?使用现代语言描述即是求满足以下条件的整数:除以 32,除以 53,除以 72

即:

{x2(mod3)x3(mod5)x2(mod7)

以上即是线性同余方程组。而《孙子算经》中记载的中国剩余定理就是解决形如以下形式的一次线性同余方程组的算法。

{xa1(modm1)xa2(modm2)xan(modmn)

其中 m1,m2,,mn 两两互质,则对于任意的整数 a1,a2,,an,上述方程有解。

求解过程如下:

  1. 计算所有模数的积 M=m1m2mn
  2. 对于第 i 个方程:
    1. 计算 bi=Mmi
    2. 计算 bi 在模 mi 意义下的逆元 bi1
    3. 计算 ci=bibi1,注意不要取模。
  3. 方程组在模 M 意义下的唯一解为:x=i=1kaici(modM)
cpp
int CRT(int k, int a[], int r[]) {
    int M = 1, ans = 0;
    for (int i = 1; i <= k; i++) M = M * r[i];
    for (int i = 1; i <= k; i++) {
        int b = M / r[i], inv_b, y;
        exgcd(b, r[i], inv_b, y);
        ans = (ans + a[i] * b * inv_b % M) % M;
    }
    return (ans % M + M) % M;
}