数学数论第二章之手把手教你同余方程

· · 算法·理论

本文将从基础到深入给大家详细讲解同余方程。

2022.9.28 动工。

基础知识

同余

同余的定义

默认地,以下所有内容都在整数范围内做研究

一般来说,如果 a\bmod p=b\bmod p,我们写成

a\equiv b\pmod p

同样滴,a\equiv b\pmod p 也可以转换成 a\bmod p=b\bmod p

同余的性质

同余的变形

如果有

f(x)\equiv g(x)\pmod p

可以变形为

f(x)+py=g(x)

其中 y 是整数。

裴蜀定理

对于一个关于 x,y 的二元一次方程

ax+by=c

其有整数解的必要条件是:

\gcd(a,b)\mid c

证明

考虑反证。

\gcd(a,b)=g

g \nmid c,则原方程有

g(\dfrac agx+\dfrac bgy)=c

\dfrac agx+\dfrac bgy=\dfrac cg

g\nmid c\dfrac cg 为分数,则 x,y 无整数解。

欧几里得算法

一种在 \Theta(\log n) 复杂度内求 \gcd(a,b) 的算法。

\gcd(a,b)= \begin{cases} a&(b=0)\\ \gcd(b,a\bmod b)&(b\ne 0) \end{cases}

复杂度网上有详细证明。我自己口胡了一个:

考虑到最坏情况下,每次 a\bmod b=a-b。而在斐波那契数列中,恰好相邻的两个满足性质。即

\gcd(f_n,f_{n-1})=\gcd(f_{n-1},f_n-f_{n-1})=\gcd(f_{n-1},f_{n-2})

也就是说 \gcd 的最劣复杂度出现在两参数为斐波那契数列相邻两项时。

而斐波那契数列第 nf_n\approx\phi^n,其中 \phi 表示黄金分割率加 1,也就是 \dfrac{\sqrt 5+1}{2}

由此可以证明,欧几里得算法求 \gcd 的复杂度是

\Theta(\log n)

的,具体的,底数为 \dfrac{\sqrt 5+1}{2}

扩展欧几里得(exgcd)

考虑一个二元一次不定方程:

ax+by=\gcd(a,b)

注意到,两个变量 a,b 与欧几里得算法有共通之处,于是就有了大名鼎鼎的扩展欧几里得(简称 exgcd)算法

考虑我们如果知道了

bx'+(a\bmod b)y'=\gcd(b,a\bmod b)

对于我们现在要解这个方程有什么帮助。

首先,显然有

\gcd(b,a\bmod b)=\gcd(a,b)

这个是根据欧几里得算法推导出来的。

根据取模的定义,

a\bmod b=a-b\times \left\lfloor\dfrac ab\right\rfloor

代入得

bx'+(a-b\times \left\lfloor\dfrac ab\right\rfloor)y'=\gcd(a,b)

等量代换,

\begin{aligned} bx'+(a-b\times \left\lfloor\dfrac ab\right\rfloor)y'&=ax+by \\ bx'+ay'-b \left\lfloor\dfrac ab\right\rfloor y'&=ax+by \\ ax+by&=ay'+b(x'-\left\lfloor\dfrac ab\right\rfloor y') \end{aligned}

于是我们可以得到 x,y 的一组解:

\begin{cases} x=y' \\ y=x'-\left\lfloor\dfrac ab\right\rfloor y' \end{cases}

当然,追根溯源,我们一开始是如何得到一组解的呢?换句话说,我们如何拿到一组解,能让我们根据这组解和上面的公式得到我们想要方程的解呢?

根据欧几里得算法,最后我们会递归到 b=0 的情况,这时我们需要求的方程是

ax=a

于是很容易就可以得到一组解了:

\begin{cases} x=1 \\ y=0 \end{cases}

代码写起来也很简单:

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

然后来看一道例题。

P1082 同余方程

求解

ax\equiv 1\pmod b

同余方程可以变形为

ax+by=1

因为题目保证一定有解,故

\gcd(a,b)\mid 1

可怜的 \gcd(a,b) 只能是 1 了,但是刚刚好等于 1,所以直接代换:

ax+by=\gcd(a,b)

然后就成了 exgcd 板子题了,但是注意到 x 跑出来有可能是负数,所以要加上 b 再取模。

#include <bits/stdc++.h>

using namespace std;

int a, b, x, y;

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

int main() {
    cin >> a >> b;
    exgcd(a, b, x, y);
    x = (x + b) % b;
    cout << x;
    return 0;
}

逆元

在做题中,我们经常遇到“对 998244353 取模”“对 10^9+7 取模”等。但是,如果我们的表达式需要做除法,那么取模后的数再除以这个数可能会与正确结果有所出入。如何解决这个问题呢?

显然,乘上一个数是可以正确取模的。问题转换为,如何得到一个数 a^{-1} 使 (a\bmod p)\times a^{-1}\bmod p=1,其中 p 是质数(为了防止逆元不存在)。a^{-1} 就表示 a 的逆元,也就是一个数除以 a 就等于乘上 a^{-1}。注意,a^{-1} 在不同 p 的情况下也是不同的。

同余方程求解法

显然,可以转化为同余方程

aa^{-1}\equiv 1\pmod p

来求解。

快速幂求解法

费马小定理

a,p 互质时,

a^{p-1}\equiv 1\pmod p

证明这里不提供,因为这毕竟是入门教程。

则有

\begin{aligned} aa^{-1}&\equiv 1\pmod p \\ aa^{-1}&\equiv a^{p-1}\pmod p \\ a^{-1}&\equiv a^{p-2}\pmod p \end{aligned}

所以 a^{-1}=a^{p-2}\bmod p

线性求逆元

1\sim n 内所有数模 p 意义下的逆元(【模板】乘法逆元)

首先,我们知道

1^{-1}=1

然后我们现在要求 i^{-1}

a=\left\lfloor\dfrac pi\right\rfloorb=p\bmod i。显然,p=ai+b

得到:

ai+b\equiv0\pmod p

两边同乘 i^{-1}b^{-1}。得

\begin{aligned} ab^{-1}+i^{-1}&\equiv0\pmod p \\ i^{-1}&\equiv -ab^{-1}\pmod p \\ i^{-1}&\equiv -\left\lfloor\dfrac pi\right\rfloor\times (p\bmod i)^{-1}\pmod p \end{aligned}

由此,我们得到:

inv[i] = -p / i * inv[p % i] % p

求任意数列的逆元(【模板】乘法逆元 2)

现有一个数列 \{a_i\},你需要在线性复杂度内对其求逆元。

s_k=\prod\limits_{i=1}^ka_i

显然,s_k 可前缀积 \Theta(n) 求出。

并且有

s_k^{-1}=\prod\limits_{i=1}^ka_i^{-1}=\left(\prod\limits_{i=1}^ka_i\right)^{-1}

易得,有

s_k^{-1}=s_{k+1}^{-1}a_{k+1}

这个也是 \Theta(n) 可以求出来的。

上式还可继续变形为

a_{k+1}^{-1}=s_ks_{k+1}^{-1}

于是就可以把这个序列的逆元线性求出来啦。

Lucas 定理

组合

这边要浅提一下组合因为后面 Lucas 定理的出现。关于排列组合的详细讲解请期待第三章。

首先给一下组合的定义:

C_n^m=\binom{n}{m}=\dfrac{n!}{m!(n-m)!}

也可以理解为,在 n 个一样的球里面选 m 个的方案数。

Lucas

https://www.luogu.com.cn/blog/28007/lucas

这篇讲的不错,我就不打了。