解各种同余方程是同余问题的一个重要部分,本文介绍各种同余方程的求解方法。
二元线性不定方程
形式
不定方程的范围很广泛,只要没有确定的解的方程都可以叫不定方程。本文主要讨论数论中的不定方程。
$$ ax+by=c\ (a,b,c,x,y\in \mathrm{Z}) $$形如上面这种形式,满足各项的系数和解均为整数的方程就是数论中的不定方程。
求解
Theorem 1:$\forall a, b \in \mathrm{Z}$,一定存在 $x,y\in \mathrm{Z}$,使得 $ax+by=\gcd(a,b)$。
这个定理又叫做裴蜀定理,根据这个定理,可以得出不定方程 $ax+by=c$ 有解的充要条件就是 $\gcd(a,b)\mid c$。记 $d=\gcd(a,b)$,则我们可以先求出 $ax'+by'=d$ 的解,再令 $x=\frac{c}{d}x',y=\frac{c}{d}y'$,就得出原方程的一组解。
求 $x',y'$ 的过程可以用扩展欧几里得算法,即 exgcd
解决。
Theorem 2:若 $\gcd(a,b)=1$,且 $x_0,y_0$ 是方程 $ax+by=c$ 的一组解,则该方程的通解为 $x=x_0+kb,y=y_0-ka$,其中 $k\in \mathrm{Z}$。
换句话说,$x,y$ 这两个解是具有周期性的。根据这个定理,我们知道一定有一个 $x\in [0,b)$,而对于方程 $ax+by=c$,则一定有一个 $x\in [0,\frac{b}{\gcd(a,b)})$,由此可以求一个不定方程的一个元的最小非负整数解,具体地,假设知道了一个解 $x_0$,则最小非负整数 $x=(x_0 \bmod m + m) \bmod m$,其中 $m=\frac{b}{\gcd(a,b)}$。
总结一下,求解二元线性不定方程 $ax+by=c$ 的流程如下:
- 求出 $ax+by=\gcd(a,b)$ 的一组解 $x',y'$,并根据 $\gcd(a,b)\mid c$ 判断方程有无解
- 令 $x=\frac{c}{\gcd(a,b)}x',y=\frac{c}{\gcd(a,b)}y'$,$x,y$ 为 $ax+by=c$ 的一组解
- 若需要求 $x$ 的最小非负整数解,则将 $x=(x_0 \bmod m + m) \bmod m$ 作为最终的答案,其中 $m=\frac{b}{\gcd(a,b)}$
线性同余方程
形式
$$ ax \equiv c \pmod{p} $$形如这样的方程,就被称线性同余方程。
求解
任意的线性同余方程都可以与一个二元线性不定方程互相转化,比如上面的方程就与二元线性不定方程有如下关系:
$$ ax \equiv c \pmod{p} \iff ax+py=c $$所以可以把线性同余方程转化为一个二元线性不定方程,求出 $x$ 即可,包括有无解的判定条件都是一样的。
代码
template <typename T> T exgcd(T a, T b, T & x, T & y) {
if (b == 0) {
x = 1;
y = 0;
return a;
} else {
T d = exgcd(b, a % b, x, y);
T t = x;
x = y;
y = t - a / b * y;
return d;
}
}
template <typename T> optional<T> solveEquation(T a, T c, T p) {
a = (a % p + p) % p;
c = (c % p + p) % p;
T x, y;
T d = exgcd(a, p, x, y);
if (c % d != 0)
return nullopt;
x = (1ll * (c / d) * x % (p / d) + (p / d)) % (p / d);
return x;
}
中国剩余定理
形式
$$ \begin{cases} x \equiv a_1 \pmod{m_1}\\\\ x \equiv a_2 \pmod{m_2}\\\\ \cdots\\\\ x \equiv a_n \pmod{m_n}\\\\ \end{cases} \\\\ \forall i\ne j,\gcd(m_i,m_j)=1 $$形如上面这样的线性同余方程组且满足每个方程的模数两两互质,就可以用中国剩余定理解决。中国剩余定理也叫做 CRT (Chinese Remainder Theorem)
。
求解
- 令 $M=\prod_{i=1}^{n} m_i$,$M$ 不对任何数取模
- 对于 $m_i$,令 $M_i=\prod_{i\ne j}m_j=\frac{M}{m_i}$,$M_i^{-1}$ 为 $M_i$ 对 $m_i$ 的逆元,$c_i=M_iM_i^{-1}$,$c_i$ 不能对 $m_i$ 取模
- 令 $x=\sum_{i=1}^{n}a_ic_i \bmod M$,则 $x$ 为模 $M$ 意义下的唯一解
算法正确性证明
对于 $\forall i\ne j$,$m_i\mid M_j$,故 $m_i\mid a_jc_j$,所以在模 $m_i$ 情况下,$x \equiv a_ic_i \pmod{m_i}$。
因为 $M_i^{-1}$ 为 $M_i$ 对 $m_i$ 的逆元,故在模 $m_i$ 的同余式中 $c_i\equiv 1 \pmod{m_i}$,$a_ic_i\equiv a_i \pmod{m_i}$,即 $x \equiv a_i \pmod{m_i}$,满足第 $i$ 个方程。
解在模 $M$ 意义下的唯一性证明
设 $x,y$ 不相等,$x,y 则 $\forall i,m_i\mid |x-y|$,所以 $M \mid |x-y|$,即 $|x-y|=kM$,因为 $|x-y| 相比普通中国剩余定理求解的方程组,少了模数互质的限制。 考虑合并方程。设当前要合并前 $i$ 个方程,显然可以转化为合并前 $i-1$ 个方程与第 $i$ 个方程。 设 $x_i$ 为合并前 $i$ 个方程的解,$d_i=\operatorname{lcm}(m_1,m_2,\dots,m_i)$。构造 $x_i=sx_{i-1}+t$,我们要使 $x_i$ 能够满足前 $i-1$ 个方程,就要满足以下条件: 最简单的办法就是令 $s=1,t=d_{i-1}y_i$,并求一个合适的 $y_i$,也就是要求出以下方程的解 $y_i$: 用 假设各个 $m_i$ 同阶,时间复杂度 $O(n \log_2 m_i)$。 与离散对数相对的是我们熟悉的连续对数,也就是最经常接触的对数。而离散对数是在同余中的一个概念。 若在模 $m$ 意义下,$x$ 满足以下等式: 则 $x$ 为以 $a$ 为底的 $b$ 的离散对数。注意离散对数不同于连续对数,一个数的离散对数有很多个,并不是唯一的。 如果 $\gcd(a,m)=1$,则我们可以使用 因为 $\gcd(a,m)=1$,则 $a^k$ 对 $m$ 的逆元一定存在。所以我们可以把 $a^x$ 写成 $a^{ps-q}$ 并直接将 $a^{-q}$ 提取出来,即: 考虑暴力的思路,可以枚举两边的 $p,q$,如果有同余的两个 $a^{ps},ba^q$,就更新答案。 考虑到枚举会计算许多重复的东西,可以用某种数据结构维护映射表,存下每个 $(ba^q,q)$,再枚举左边找相应的同余的项。我们先假设 $x=ps-q \in [0,m)$,则当 $s=\lceil\sqrt{m}\rceil$ 时,时间复杂度最优,为 $\Theta(\sqrt{m})$ 或 $\Theta(\sqrt{m}\log_2 \sqrt{m})$,具体为哪种复杂度,取决于是用哈希表还是平衡树实现。 至于为什么一定有 $x\in [0,m)$,使用抽屉原理证明: 对于任意的 $x$,$a^x \bmod m$ 最多只有 $m$ 种取值,而在 $x\in [0,m]$ 时,一共有 $m+1$ 种指数,则根据抽屉原理得出必定存在一对 $x,y$ 满足 $a^x \equiv a^y \pmod{m}$。设 $t=|x-y|$,则 $a^x a^k=a^{x+t}a^k$,因为已经保证 $\gcd(a,m)=1$,则逆元一定存在,所以可以规定 $t\in\mathrm{Z}$,所以指数具有周期性,一定可以得到 $x\in [0,m)$。 其中 $a,m$ 不一定互质。这种情况下,逆元不一定存在,也就不可以把指数拆成两个部分来优化。 考虑先把式子化为 $\gcd(a,m)=1$ 的形式。方法是不断提取出 $a$ 与 $m$ 互质的部分,任何整个式子同除以这个数。 这个式子是同余式的基本性质。所以按照这样的方法提取: 假设现在 $a$ 与 $\frac{m}{d_1 d_2\cdots d_k}$ 互质,就可以用 需要注意两点: 对于一对互质的整数 $a,m$,若 $r$ 满足 $r \ne 0$ 且 $a^r \equiv 1 \pmod{m}$ 且不存在任何小于 $r$ 的正整数满足该同余式,则称 $r$ 为 $a$ 对 $m$ 的阶,记作 $r = \delta_m(a)$。 Theorem 1:$0 < \delta_m(a) \le \varphi(m)$。 若 $\gcd(a,m)=1$,则欧拉定理一定成立,即 $a^{\varphi(m)} \equiv 1 \pmod{m}$,显然 $0 < \delta_m(a) \le \varphi(m)$。 Theorem 2:若 $a^k \equiv 1 \pmod{m}$,则 $\delta_m(a) \mid k$。 假设满足 $\delta_m(a) \nmid k$ 但 $a^k \equiv 1 \pmod{m}$,则 $k=s\delta_m(a)+t$,其中 $t \in (0,\delta_m(a))$,所以: 根据阶的定义,$a^t \not\equiv 1 \pmod{m}$,所以矛盾,故 $\delta_m(a) \mid k$。 若 $a$ 满足 $\delta_m(a)=\varphi(m)$,则 $a$ 为 $m$ 的原根。 Theorem 1:$a$ 为 $m$ 的原根的充要条件为 $\gcd(a,m)=1$ 且对于 $\varphi(m)$ 的每一个质因数,$a^{\frac{\varphi(m)}{p}} \not\equiv 1 \pmod{m}$。 根据这个定理,可以轻松判断一个数是否是另一个数的原根。 Theorem 2:一个数 $m$ 有原根,当且仅当 $m$ 为 $2,4,p^k,2p^k$,其中 $k\in \mathrm{N^*}$,$p$ 为奇质数。 Theorem 3:若一个数 $m$ 有原根,则其原根个数为 $\varphi(\varphi(m))$。 Theorem 4:若一个数 $m$ 有原根,则其最小原根的数量级为 $m^{\frac{1}{4}}$。 根据这个性质,只需要暴力枚举每个数,求一个数的最小原根的时间复杂度是可以接受的。 Theorem 5:若 $a$ 为 $m$ 的原根,则 $a,a^2,a^3,\dots,a^{\varphi(m)}$ 两两模 $m$ 不同余。 Theorem 6:若 $a$ 为 $m$ 的原根,则对于任意的 $x \in [1,\varphi(m)]$,一定有一个 $k$ 与其一一对应,满足 $x \equiv a^k \pmod{m}$ 且 $k\in [1,\varphi(m)]$,$k$ 被称为 $x$ 的指标,记作 $I(x)$。 比如 $m=13$,其最小原根 $a=2$,则把 $x$ 与 $I(x)$ 写成表格: 这个定理可以用于把底数转换为原根的幂,可以利用这个性质解模数为质数的 $N$ 次剩余问题。 其中 $m$ 为奇质数,求 $x\in [0,m)$。由于 $m$ 不是奇质数的方法较复杂,这里不考虑。 设 $g$ 为其原根,则可以利用原根和指标的性质将 $x$ 转化为 $g^y$,即: 这样就可以用 也可以再将 $a$ 化为 $g^z$,然后方程化为: 于是就可以解出线性同余方程的解 $y$: 代码就不写了,上面组合一下就可以了。代码
int exgcd(int a, int b, int & x, int & y) {
if (b == 0) {
x = 1;
y = 0;
return a;
} else {
int d = exgcd(b, a % b, x, y);
int t = x;
x = y;
y = t - (a / b) * y;
return d;
}
}
int solveEquation(int a, int c, int p) {
int x, y;
exgcd(a, p, x, y);
x = (1ll * x * c % p + p) % p;
return x;
}
inline int inv(int x, int p) {
return solveEquation(x, 1, p);
}
int chineseRemainderTheorem(int n, int a[], int m[]) {
int mod = 1, ans = 0;
for (int i = 1; i <= n; ++i)
mod *= m[i];
for (int i = 1; i <= n; ++i) {
int modi = mod / m[i];
ans = (ans + 1ll * a[i] * modi * inv(modi)) % mod; // 用 exgcd 求逆元
}
return ans;
}
扩展中国剩余定理
形式
$$
\begin{cases}
a_1 x \equiv c_1 \pmod{m_1}\\\\
a_2 x \equiv c_2 \pmod{m_2}\\\\
\cdots\\\\
a_n x \equiv c_n \pmod{m_n}\\\\
\end{cases}
$$求解
exgcd
解决,若该线性同余方程无解,则方程组无解,否则求出前 $i$ 个方程的解 $x_i$,进行下一次合并。代码
int gcd(int a, int b) {
return b == 0 ? a : gcd(b, a % b);
}
int exgcd(int a, int b, int & x, int & y) {
if (b == 0) {
x = 1;
y = 0;
return a;
} else {
int d = exgcd(b, a % b, x, y);
int t = x;
x = y;
y = t - (a / b) * y;
return d;
}
}
optional<int> solveEquation(int a, int c, int p) {
a = (a % p + p) % p;
c = (c % p + p) % p;
int x, y, d = exgcd(a, p, x, y);
if (c % d)
return nullopt;
x *= (c / d);
int m = p / d;
x = (x % m + m) % m;
return x;
}
optional<int> exChineseRemainderTheorem(int n, int a[], int c[], int m[]) {
int x = 0, d = 1;
for (int i = 1; i <= n; ++i) {
auto oy = solveEquation(a[i] * d, c[i] - a[i] * x, m[i]);
if (!oy.has_value())
return nullopt;
int y = oy.value();
int g = gcd(d, m[i]);
d = d / g * m[i];
x = (x + 1ll * (d / m[i] * g) * y) % d;
}
return x;
}
BSGS
离散对数
BSGS
算法求解一个数的最小的离散对数。求解
可解性证明
代码
int power(int x, int y, int m) {
int res = 1;
for (; y; y /= 2) {
if (y % 2)
res = 1ll * res * x % m;
x = 1ll * x * x % m;
}
return res;
}
optional<int> bsgs(int a, int b, int m) {
a %= m;
b %= m;
unordered_map<int, int> buc;
int s = ceil(sqrt(m)), prod = b, base = power(a, s, m);
buc[b] = 0;
for (int i = 0; i < s; ++i) {
prod = 1ll * prod * a % m;
buc[prod] = i;
}
prod = 1;
for (int i = 0; i <= s; ++i) {
auto it = buc.find(prod);
if (it != buc.end() && i * s - it->second >= 0)
return i * s - it->second;
prod = 1ll * prod * base % m;
}
return nullopt;
}
扩展 BSGS
形式
$$
a^x \equiv b \pmod{m}
$$BSGS
求出最后一个方程的解 $x-k$,再加上 $k$ 就是 $x$ 了。int gcd(int a, int b) {
int t;
while (b) {
t = a;
a = b;
b = a % b;
}
return a;
}
int power(int x, int y, int m) {
int res = 1;
for (; y; y /= 2) {
if (y % 2)
res = 1ll * res * x % m;
x = 1ll * x * x % m;
}
return res;
}
int exgcd(int a, int b, int & x, int & y) {
if (b == 0) {
x = 1;
y = 0;
return a;
} else {
int d = exgcd(b, a % b, x, y);
int t = x;
x = y;
y = t - a / b * y;
return d;
}
}
int inverse(int a, int p) {
int x, y;
exgcd(a, p, x, y);
x = (x % p + p) % p;
return x;
}
optional<int> bsgs(int a, int b, int m) {
a %= m;
b %= m;
unordered_map<int, int> buc;
int s = ceil(sqrt(m)), prod = b, base = power(a, s, m);
buc[b] = 0;
for (int i = 0; i < s; ++i) {
prod = 1ll * prod * a % m;
buc[prod] = i;
}
prod = 1;
for (int i = 0; i <= s; ++i) {
auto it = buc.find(prod);
if (it != buc.end() && i * s - it->second >= 0)
return i * s - it->second;
prod = 1ll * prod * base % m;
}
return nullopt;
}
optional<int> exBsgs(int a, int b, int m) {
a = (a % m + m) % m;
b = (b % m + m) % m;
if (m == 1 || b == 1)
return 0;
int prod = 1, d, k = 0;
while (true) {
d = gcd(a, m);
if (d == 1)
break;
if (b % d)
return nullopt;
b /= d;
m /= d;
++k;
prod = prod * (a / d) % m;
if (prod == b)
return k;
}
auto res = bsgs(a, 1ll * b * inverse(prod, m) % p, m);
if (!res.has_value())
return nullopt;
return res.value() + k;
}
阶与原根
阶
原根
int gcd(int a, int b) {
int t;
while (b) {
t = a;
a = b;
b = t % b;
}
return a;
}
int phi(int x) {
int res = x;
for (int i = 2; i * i <= x; ++i) {
if (x % i)
continue;
res = res / i * (i - 1);
while (x % i)
x /= i;
}
if (x > 1)
res = res / x * (x - 1);
return res;
}
int power(int x, int y, int m) {
int res = 1;
for (; y; y /= 2) {
if (y % 2)
res = 1ll * res * x % m;
x = 1ll * x * x % m;
}
return res;
}
bool isPrimitiveRoot(int a, int m) {
if (gcd(a, m) != 1)
return false;
int pm = phi(m), tmp = pm;
a %= m;
for (int i = 2; i * i <= tmp; ++i) {
if (tmp % i)
continue;
if (power(a, pm / i, m) == 1)
return false;
while (tmp % i == 0)
tmp /= i;
}
if (tmp > 1)
if (power(a, pm / tmp, m) == 1)
return false;
return true;
}
int gcd(int a, int b) {
int t;
while (b) {
t = a;
a = b;
b = t % b;
}
return a;
}
int phi(int x) {
int res = x;
for (int i = 2; i * i <= x; ++i) {
if (x % i)
continue;
res = res / i * (i - 1);
while (x % i)
x /= i;
}
if (x > 1)
res = res / x * (x - 1);
return res;
}
int power(int x, int y, int m) {
int res = 1;
for (; y; y /= 2) {
if (y % 2)
res = 1ll * res * x % m;
x = 1ll * x * x % m;
}
return res;
}
int primitiveRoot(int m) {
int pm = phi(m), tmp = pm;
vector<int> factor;
for (int i = 2; i * i <= tmp; ++i) {
if (tmp % i)
continue;
factor.push_back(i);
while (tmp % i == 0)
tmp /= i;
}
if (tmp > 1)
factor.push_back(tmp);
for (int i = 2; i <= m; ++i) {
if (gcd(i, m) != 1)
continue;
bool found = true;
for (int p : factor) {
if (power(i, pm / p, m) == 1) {
found = false;
break;
}
}
if (found)
return i;
}
return -1;
}
$x$ $1$ $2$ $3$ $4$ $5$ $6$ $7$ $8$ $9$ $10$ $11$ $12$ $I(x)$ $12$ $1$ $4$ $2$ $9$ $5$ $11$ $3$ $8$ $10$ $7$ $6$ N 次剩余
形式
$$
x^n \equiv a \pmod{m}
$$求解
BSGS
求出 $y$,再用快速幂求得 $x$ 即可。