再谈数论
Loyal_Soldier
·
2026-07-06 15:27:40
·
算法·理论
由于本人是小升区 xxs,数学学的不多,文章有错误还请多多谅解。
浅谈数论
由普及组二等小朋友变成了提高组三等小朋友 / 咦
使用 DeepSeek 优化了一下 Markdown。
二元一次不定方程
虽然是【模板】exgcd,但比 exgcd 难多了
对于不定方程 ax + by = c ,求:
若该方程有整数解,且有正整数解,求正整数解的数量、正整数解中 x 的最小值、正整数解中 y 的最小值、正整数解中 x 的最大值、正整数解中 y 的最大值。
若该方程有整数解,没有正整数解,求整数解中 x 的最小正整数值、y 的最小正整数值。
显然,根据裴蜀定理,如果 \gcd(a, b) \nmid c ,则方程无解。
::::info[裴蜀定理]
设 a, b 是不全为零的整数,那么,对于任意整数 x, y ,都有 \gcd(a, b) \mid ax+by 成立,并且,存在整数 x, y ,使得 ax + by = \gcd(a, b) 成立。
:::info[证明]
设 d = \gcd(a, b) 。因为 d \mid a, b ,所以存在整数 p, q ,使得 a = pd, b = qd 成立,因此,总有:
ax + by = d(px + qy)
说明 d \mid ax + by 。
考虑 a, b 都 \ne 0 的情况,如果 a 或 b 是 0 ,显然有 (x, y) = (1, 0)/(0, 1) 使得 ax + by = \gcd(a, b) 成立。
因为 \gcd(a, b) = \gcd(-a, b) = \gcd(a, -b) ,设 a, b > 0 。
考虑辗转相除:
\begin{cases}
a = q_1b + r_1, & 0 \le r_1 < b \\
b = q_rr_1 + r_2, & 0 \le r_2 < r_1
\end{cases}
\begin{cases}
r_1= q_3r_2 + r_3, & 0 \le r_3 < r_2 \\
r_2= q_4r_3 + r_4, & 0 \le r_4 < r_3 \\
\dots \\
r_{n - 2} = q_nr_{n - 1} + r_n, & 0 \le r_{n - 1} < r_{n - 1} \\
r_{n - 1} = q_{n + 1}r_n,
\end{cases}
因为最大公因数是 d ,所以 r_n = d ,根据倒数第二个等式,那么:
d = r_n = r_{n - 2} - q_nr_{n - 1}
根据倒数第三个等式:
r_{n - 1} = r_{n - 3} - q_{n - 1}r_{n - 2}
带入上式:
\begin{aligned}
d &= r_{n - 2} - q_n(r_{n - 3} - q_{n - 1}r_{n - 2}) \\
&= (1 + q_nq_{n - 1})r_{n - 2} - q_nr_{n - 3}
\end{aligned}
以此类推,逐步消去 r_{n - 2}, r_{n - 3}, \dots ,可以得到:
d = xa + yb
证毕。
:::
::::
首先,使用 exgcd 求出 ax + by = \gcd(a, b) 的解,记为 x_0, y_0 ,则有:
a\frac{x_0c}{\gcd(a, b)} + b\frac{y_0c}{\gcd(a, b)} = c
显然,我们找到了原方程的一组特解 x_1 = \frac{x_0c}{\gcd(a, b)}, y_1 = \frac{y_0c}{\gcd(a, b)} 。
我们设 a(x_1 + m) + b(y_1 + n) = c ,考虑怎么求出 m, n 。
将式子展开可以发现,需要让 am + bn = 0 。
我们让 m = t \times \frac{b}{\gcd(a, b)}, n = t \times \frac{a}{\gcd(a, b)}(t \in \mathbb{Z}) ,带入发现 am + bn = 0 ,于是通解为:
\begin{cases}
x = x_1 + t \times \frac{b}{\gcd(a, b)} \\
y = y_1 - t \times \frac{a}{\gcd(a, b)}
\end{cases}
x 最小值
显然,我们要找出最小的 t 使得 x \ge 1 。
x \ge 1 \\
x_1 + t \times \frac{b}{\gcd(a, b)} \ge 1 \\
t \times \frac{b}{\gcd(a, b)} \ge 1 - x_1 \\
t \ge \left\lceil \frac{1 - x_1}{\frac{b}{\gcd(a, b)}} \right\rceil
显然可以直接求。
y 最大值
根据 ax + by = c ,可得 y = \frac{c - ax}{b} 。
注意到 y 在 \mathbb{R} 上单调递减,x 取到 x_{\min} 再求出 y_{\max} 即可。
如果 y_{\max} \le 0 ,就没有正整数解。
y 最小值
考虑 ax_{\max} + by_{\min} = c 这个方程。
由于 ax_{\min} + by_{\max} = c ,设 \Delta x = x_{\max} - x_{\min}, \Delta y = y_{\min} - y_{\max} 。
得:
\begin{cases}
\Delta x = t \times \frac{b}{\gcd(a, b)} \\
\Delta y = -t \times \frac{a}{\gcd(a, b)}
\end{cases}
根据
\begin{aligned}
y_{\min} &= \Delta y + y_{\max} \\
&= y_{\max} - t \times \frac{a}{\gcd(a, b)}
\end{aligned}
说明
y_{\min} \equiv y_{\max} \pmod{\frac{a}{\gcd(a, b)}}
由于 y_{\min} 必须是最小正整数,得:
y_{\min} =
\begin{cases}
y_{\max} \bmod \frac{a}{\gcd(a, b)}, & y_{\max} \bmod \frac{a}{\gcd(a, b)} \ne 0 \\
\frac{a}{\gcd(a, b)}, & y_{\max} \bmod \frac{a}{\gcd(a, b)} = 0
\end{cases}
x 最大值
和 y_{\max} 一样,把 y_{\min} 代入即可。
解的个数
我们在 (y_{\min}, y_{\max}] 中寻找解。
考虑变换一下通解格式:
\begin{cases}
x' = y - t \times \frac{b}{\gcd(a, b)} \\
y' = x + t \times \frac{a}{\gcd(a, b)}
\end{cases}
显然,每隔 \frac{a}{\gcd(a, b)} 个就会存在一个解,那么解的个数为 \frac{y_{\max} - y_{\min}}{\frac{a}{\gcd(a, b)}} + 1 。
对于正无整数解的情况,也是类似的。
::::success[code]
while (T--) {
int a, b, c;
cin >> a >> b >> c;
exgcd(a, b);
int qwq = __gcd(a, b);
if (c % qwq) {
cout << "-1\n";
continue;
}
x = x * c / qwq;
y = y * c / qwq;
int X = b / qwq, Y = a / qwq;
int awa = ceil((1.0 - x) / X);
x += X * awa;
y -= Y * awa;
if (y <= 0) {
int y_min = y + Y * ceil((1.0 - y) / Y);
cout << x << ' ' << y_min << '\n';
}
else cout << (y - 1) / Y + 1 << ' ' << x << ' ' << (y - 1) % Y + 1 << ' ' << x + (y - 1) / Y * X << ' ' << y << '\n';
}
::::
推荐习题
Lucas 定理
对于 p 是质数,有 \binom{n}{m} = \binom{\left\lfloor \frac{n}{p} \right\rfloor}{\left\lfloor \frac{m}{p} \right\rfloor} \binom{n \bmod p}{m \bmod p} \pmod p 。
:::::info[证明]{open}
::::info[引理]
对于质数 p 和整数 x ,有 (1 + x)^p \equiv 1 + x^p \pmod p 。
:::info[引理的证明]
由二项式定理可得 (1 + x)^p = \sum_{k = 0}^p \binom{p}{k} x^k 。
当 0 < k < p 时,显然 \binom{p}{k} 可以被 p 整除,那么 (1 + x)^p \equiv 1 + x^p \pmod p 。
:::
::::
考虑将 n, m 变一下形式
\begin{cases}
n = sp + q, & q = n \bmod p \\
m = tp + r, & r = m \bmod p
\end{cases}
考虑将 (1 + x)^n 展开,得到 (1 + x)^n = (1 + x)^{sp + q} = ((1 + x)^p)^s (1 + x)^q 。
将 (1 + x)^q 带入引理,可得:
(1 + x)^n \equiv (1 + x^p)^s (1 + x)^q \pmod p
我们来比较两边 x^m 的项数。
左边:(1 + x)^n = \sum_{k = 0}^n \binom{n}{k} x^k ,则 x^m 的系数就是 \binom{n}{m} 。
右边:
将第一项展开 (1 + x^p)^s = \sum_{t = 0}^s \binom{s}{t} x^{tp}
将第二项展开 (1 + x)^q = \sum_{r = 0}^q \binom{q}{r} x^r
某一项指数为 tp + r ,注意到 m = tp + r 这一种表示方法是唯一的,所以右边系数只能是 \binom{s}{t} \binom{q}{r} 。
可得 \binom{n}{m} = \binom{s}{t} \binom{q}{r} ,转变一下形式就是:
\binom{n}{m} = \binom{\left\lfloor \frac{n}{p} \right\rfloor}{\left\lfloor \frac{m}{p} \right\rfloor} \binom{n \bmod p}{m \bmod p} \pmod p
证毕。
:::::
::::success[code]
int C(int n, int k, int mod) {
if (k < 0 || k > n) return 0;
return fact[n] * inv[k] % mod * inv[n - k] % mod;
}
int Lucas(int n, int k, int mod) {
if (k == 0) return 1;
return C(n % mod, k % mod, mod) * Lucas(n / mod, k / mod, mod) % mod;
}
::::
例题 P2480
数论大杂烩
给一个整数 n ,求:
g^{\sum_{d \mid n} \binom{n}{d}} \bmod 999911659
如果 999911659 \mid g ,答案就是 0 。
注意到模数 999911659 是一个质数,考虑对其使用费马小定理。
::::::info[费马小定理]
若整数 p 为质数 且 p 与整数 a 互质 ,则满足:
a^{p - 1} \equiv 1 \pmod p
:::::info[证明]
因为 p 是质数,那么 \varphi(p) = p - 1 ,这时就转化成了欧拉定理的一种情况,由于满足欧拉定理,费马小定理一定成立。
::::info[欧拉定理]
若正整数 a, n 互质 ,则:
a^{\varphi(n)} \equiv 1 \pmod n
其中 \varphi(n) 为欧拉函数,表示 1 \sim n 中与 n 互质的数的个数。
:::info[证明]
设 x_1, x_2, \dots, x_{\varphi(n)} 为 1 \sim n 中与 n 互质的数。
这时,我们发现 a \times x_1, a \times x_2, \dots, a \times x_{\varphi(n)} 具有这个性质:每个数 \bmod n 两两不同,且余数与 n 互质。而且,x_1, x_2, \dots, x_{\varphi(n)} 也具有这个性质。
有了这个性质,将 a \times x_1, a \times x_2, \dots, a \times x_{\varphi(n)} 都 \bmod n 后一定是 \varphi(n) 个不同的与 n 互质的数,那么:
x_1 \times x_2 \times \dots \times x_{\varphi(n)} \equiv a \times x_1 \times a \times x_2 \times \dots \times a \times x_{\varphi(n)} \pmod n \\
1 \equiv a^{\varphi(n)} \pmod n
:::
::::
:::::
::::::
然后,原式变成了:
g^{\sum_{d \mid n} \binom{n}{d} \bmod 999911658} \bmod 999911659
考虑怎么求 \sum_{d \mid n} \binom{n}{d} \bmod 999911658 。
将 $999911658$ 分解质因数,得到 $999911658 = 2 \times 3 \times 4679 \times 35617$。
设 $\sum_{d \mid n} \binom{n}{d}$ 为 $S$,我们可以列出以下同余方程组:
$$
\begin{cases}
S \equiv a_1 & \pmod 2 \\
S \equiv a_2 & \pmod 3 \\
S \equiv a_3 & \pmod {4679} \\
S \equiv a_4 & \pmod {35617}
\end{cases}
$$
显然,可以使用 CRT 求出方程的解 $S$,对于 $S \bmod p$,用 Lucas 定理求解即可。
::::success[code]
```cpp
int MOD[MAXN] = {0, 2, 3, 4679, 35617};
int C(int n, int k, int mod) {
if (k < 0 || k > n) return 0;
return fact[n] * inv[k] % mod * inv[n - k] % mod;
}
int Lucas(int n, int k, int mod) {
if (k == 0) return 1;
return C(n % mod, k % mod, mod) * Lucas(n / mod, k / mod, mod) % mod;
}
const int mod1 = 999911659;
const int mod2 = 999911658;
int CRT() {
int ans = 0;
for (int i = 1; i <= tot_mod; i++) {
int qwq = mod2 / MOD[i];
int awa = qpow(qwq, MOD[i] - 2, MOD[i]);
ans = (ans + qwq * a[i] % mod2 * awa % mod2) % mod2;
}
return (ans + mod2) % mod2;
}
void get(int x) {
init(MOD[x]);
for (int i = 1; i <= tot_d; i++) a[x] = (a[x] + Lucas(n, d[i], MOD[x])) % MOD[x];
}
```
::::
## 扩展 Lucas 定理 / exLucas
~~说实话,这个定理跟 Lucas 定理没有半毛钱关系~~\
~~唉,我讲了 exLucas 为什么还要讲 Lucas 定理~~
> 求 $\binom{n}{m} \bmod p$,$p$ 不一定是质数。
设 $p = p_1^{k_1} \times p_2^{k_2} \times \dots$。
于是,可以用 CRT 求出以下方程的解,解就是 $\binom{n}{m}$:
$$
\begin{cases}
\binom{n}{m} \equiv a_1 & \pmod {p_1^{k_1}} \\
\binom{n}{m} \equiv a_2 & \pmod {p_2^{k_2}} \\
\dots
\end{cases}
$$
现在考虑怎么求出 $\binom{n}{m} \bmod p^k$。
$$
\binom{n}{m} \bmod p^k = \frac{n!}{m!(n - m)!} \bmod p^k
$$
把式子转换成
$$
\frac{\frac{n!}{p^x}}{\frac{m!}{p^y} \frac{(n - m)!}{p^z}} \bmod p^{x - y - z}
$$
$x, y, z$ 为 $n!, m!, (n - m)!$ 包含多少 $p$ 的因子。
考虑怎么求出 $\frac{n!}{p^x}$,其他照猫画虎。
$$
\begin{aligned}
n!
&= (1 \times 2 \times 3 \times \cdots)(p \times 2p \times 3p \times \cdots) \\
&= p^{\left\lfloor \frac{n}{p} \right\rfloor} (1 \times 2 \times \cdots) (1 \times 2 \times 3 \times \cdots) \\
&= p^{\left\lfloor \frac{n}{p} \right\rfloor} \times \left(\left\lfloor \frac{n}{p} \right\rfloor\right)! \times \prod_{\substack{i=1 \\ i \not\equiv 0 \pmod p}}^n i
\end{aligned}
$$
注意到这个 $\pmod p$ 显然是有循环节的。
于是:
$$
n! = p^{\left\lfloor \frac{n}{p} \right\rfloor} \times \left(\left\lfloor \frac{n}{p} \right\rfloor\right)! \times \left(\prod_{i = 1, i \not\equiv 0 \pmod p}^{p^k} i\right)^{\left\lfloor \frac{n}{p^k} \right\rfloor} \left(\prod_{i = p^k \left\lfloor \frac{n}{p^k} \right\rfloor, i \not\equiv 0 \pmod p}^n i\right)
$$
第三部分是循环节的乘积,最后是循环后面剩下的乘积。
注意到 $p^{\left\lfloor \frac{n}{p} \right\rfloor}$ 是要除掉的,而 $\left(\left\lfloor \frac{n}{p} \right\rfloor\right)!$ 可能还包含 $p$。
定义 $f(n) = \frac{n!}{p^k}$。
$$
f(n) = f\left(\left\lfloor \frac{n}{p} \right\rfloor\right) \left(\prod_{i=1, i\not\equiv 0 \pmod p}^{p^k} i\right)^{\left\lfloor \frac{n}{p^k} \right\rfloor} \left(\prod_{i=p^k \left\lfloor \frac{n}{p^k} \right\rfloor, i\not\equiv 0 \pmod p}^n i\right)
$$
这样就好了。时间复杂度是 $O(\log_p n)$。
边界 $f(0) = 1$。
看看原式
$$
\frac{ \frac{n!}{p^x} }{ \frac{m!}{p^y} \frac{(n-m)!}{p^z} } \cdot p^{\,x-y-z} \bmod p^k \\
= \frac {f(n)}{f(m)f(n-m)} p^{x-y-z} \bmod p^k
$$
考虑 $p^{x - y - z}$ 怎么求。
设 $g(n) = x$(上述 $p^{x - y - z}$ 中的 $x$)。
我们查看原来的式子,可以发现,$n!$ 中显然有 $ \left\lfloor \frac{n}{p} \right\rfloor$ 个 $p$,然后还剩 $\left\lfloor \frac{n}{p} \right\rfloor !$ 的 $p$ 还没有求出,可以递归到 $g\left(\left\lfloor \frac{n}{p} \right\rfloor\right)$。
所以我们可以得到以下递推式
$$
g(n) = \left\lfloor \frac{n}{p} \right\rfloor + g\left(\left\lfloor \frac{n}{p} \right\rfloor\right)
$$
时间复杂度是 $O(\log_p n)$。
边界 $g(n) = 0 \ (n < p)$。
所以答案就是
$$
\frac {f(n)}{f(m)f(n-m)} p^{g(n)-g(m)-g(n-m)} \bmod p^k
$$
::::success[code]
```cpp
int exgcd(int a, int b, int &x, int &y) {
if (!b) {
x = 1, y = 0;
return a;
}
int d = exgcd(b, a % b, y, x);
y -= a / b * x;
return d;
}
int inv(int a, int p) {
int x, y;
exgcd(a, p, x, y);
return (x % p + p) % p;
}
int qpow(int a, int b, int p) {
int ans = 1;
a %= p;
while (b) {
if (b & 1) ans = ans * a % p;
a = a * a % p;
b >>= 1;
}
return ans;
}
int f(int n, int p, int PK) {
if (n == 0) return 1;
int res = 1, ans = 1;
for (int i = 1; i <= PK; i++)
if (i % p) res = res * i % PK;
res = qpow(res, n / PK, PK);
for (int i = PK * (n / PK); i <= n; i++)
if (i % p) ans = ans * (i % PK) % PK;
return f(n / p, p, PK) * res % PK * ans % PK;
}
int g(int n, int p) {
if (n < p) return 0;
return n / p + g(n / p, p);
}
int C_mod(int n, int m, int p, int PK) {
int a = f(n, p, PK), b = inv(f(m, p, PK), PK), c = inv(f(n - m, p, PK), PK);
int qwq = qpow(p, g(n, p) - g(m, p) - g(n - m, p), PK);
return a * b % PK * c % PK * qwq % PK;
}
const int MAXN = 1e5 + 10;
int A[MAXN], B[MAXN];
void exLucas(int n, int m, int p) {
int now = p;
int tot = 0;
for (int i = 2; i * i <= p; i++) {
if (now % i == 0) {
int PK = 1;
while (now % i == 0) {
now /= i;
PK *= i;
}
A[++tot] = PK;
B[tot] = C_mod(n, m, i, PK);
}
}
if (now > 1) {
A[++tot] = now;
B[tot] = C_mod(n, m, now, now);
}
int ans = 0;
for (int i = 1; i <= tot; i++) {
int qwq = p / A[i], awa = inv(qwq, A[i]);
ans = (ans + B[i] * qwq % p * awa % p) % p;
}
cout << ans;
}
```
::::
## 扩展中国剩余定理 / exCRT
CRT 详见浅谈数论。
~~和 exLucas 一样,exCRT 和 CRT 也没有半毛钱关系~~
> 求解以下同余方程组:
> $$
> \begin{cases}
> x \equiv a_1 & \pmod {b_1} \\
> x \equiv a_2 & \pmod {b_2} \\
> \dots \\
> x \equiv a_n & \pmod {b_n}
> \end{cases}
> $$
> 其中 $a$ 是正整数,$b$ 是非负整数。
考虑从头开始,一个一个合并这些同余方程。
设当前同余方程为 $x \equiv B \pmod A$,需要和方程 $x \equiv b \pmod a$ 合并。
显然,可以得到:
$$
x + AX = B \ (X \in \mathbb{Z}) \\
x + aY = b \ (Y \in \mathbb{Z})
$$
整理两个方程:
$$
B - AX = b - aY \\
AX - aY = B - b
$$
可以使用 exgcd 求出一组特解 $x_0, y_0$,再求解一组通解:
$$
X = x_0 + \frac{a}{\gcd(a, A)} \times k \\
Y = y_0 - \frac{A}{\gcd(a, A)} \times k
$$
将通解带入:
$$
\begin{aligned}
x &= B - AX \\
&= B - A\left(x_0 + \frac{a}{\gcd(a, A)} \times k\right) \\
&= B - Ax_0 - \frac{A \times a}{\gcd(a, A)} \times k \\
&= B - Ax_0 - \operatorname{lcm}(a, A) \times k
\end{aligned}
$$
显然,最后是一个同余方程:
$$
x \equiv B - Ax_0 \pmod {\operatorname{lcm}(a, A)}
$$
依次合并每个方程即可,注意初始时 $A = 1, B = 0$。
::::success[code]
```cpp
#include <bits/stdc++.h>
#define int long long
using namespace std;
int n, A, B;
int mul(int a, int b, int mod) {
int ans = 0;
while (b) {
if (b & 1) ans = (ans + a) % mod;
a = (a + a) % mod;
b >>= 1;
}
return ans;
}
int x, y;
void exgcd(int a, int b) {
if (b == 0) {
x = 1, y = 0;
return;
}
exgcd(b, a % b);
int Gcd = x;
x = y;
y = Gcd - a / b * y;
}
signed main() {
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
cin >> n;
A = 1, B = 0;
for (int i = 1; i <= n; i++) {
int a, b;
cin >> a >> b;
exgcd(A, a);
int d = __gcd(A, a);
int k = a / d;
int qwq = (((B - b) / d) % k + k) % k;
x = (x % k + k) % k;
x = mul(x, qwq, k);
B = B - A * x;
A = A / d * a;
B %= A;
if (B < 0) B += A;
}
cout << B % A;
return 0;
}
```
::::
### 例题 P4774
> 有 $n$ 条巨龙,你要按顺序击杀。
> 初始有 $m$ 把剑,攻击力已知。
>
> 面对第 $i$ 条巨龙时:
> - 从当前剑中选出攻击力不超过 $a_i$ 的最大的一把;若不存在,则选攻击力最小的。
> - 用这把剑(攻击力记为 $ATK_i$)攻击 $x$ 次,造成 $x \cdot ATK_i$ 伤害。
> - 巨龙初始生命值为 $a_i$,每次恢复 $p_i$ 生命值。
> 要求经过若干次恢复后,生命值恰好为 $0$。
>
> 你需要找到一个最小的整数 $x$,使得对所有 $i$ 都成立。
> 若不存在这样的 $x$,输出 $-1$。
显然,每一条龙都有一把对应的剑,我们使用 multiset 预处理出每一条龙对应的剑的攻击力 $b_i$。
于是,题意转化为求解以下同余方程组的最小非负整数解:
$$
\begin{cases}
b_1x \equiv a_1 & \pmod {p_1} \\
b_2x \equiv a_2 & \pmod {p_2} \\
\dots \\
b_nx \equiv a_n & \pmod {p_n}
\end{cases}
$$
考虑使用 exCRT 求解,和模板相比,换汤不换药。
按照 exCRT 的推导方式,设当前在合并第 $i$ 个方程,令 $g_1 = \gcd(b_i, p_i)$,$b = \frac{a_i}{g_1} \cdot \left(\frac{b_i}{g_1}\right)^{-1} \bmod {\frac{p_i}{g_1}}$,$g = \gcd(A, a)$,则:
$$
x \equiv B - A \cdot \left( x_0 \cdot \frac{B - b}{g} \right) \pmod{\operatorname{lcm}(A, a)}
$$
注意判断无解。
## 大步小步算法 / BSGS
> 给定三个整数 $a, b, p$,保证 $a \perp p$,求一个最小的非负整数 $x$,使得 $a^x \equiv b \pmod p$。
定义 $x = im - j, m = \left\lceil \sqrt p \right\rceil$,其中 $1 \le i \le m, 0 \le j \le m$,显然,对于 $x \in [0, p]$ 都可以用 $im - j$ 表示出来。
将 $im - j$ 代入,得:
$$
a^{im - j} \equiv b \pmod p \\
(a^m)^i \equiv b \cdot a^j \pmod p
$$
我们枚举 $j$,令 $s = b \cdot a^j \bmod p, t = j$,放入一个 `unordered_map`,如果 $s$ 重复,使用更大的 $t$ 替换小的。
枚举 $i$,查找哈希表中是否有 $s = (a^m)^i$,如果有,说明我们找出了一组解 $x = im - j$。
由于 $i, j \le \left\lceil \sqrt p \right\rceil$,显然时间复杂度是 $O(\sqrt p)$。
::::success[code]
```cpp
// b, n, p 为题目中的 b, n, p
int BSGS() {
int qwq = sqrt(p);
if (qwq * qwq != p) qwq++;
mp[n] = 0;
for (int i = 1; i <= qwq; i++) {
n = (n * b) % p;
mp[n] = i;
}
int Pow = 1;
for (int i = 1; i <= qwq; i++) Pow = (Pow * b) % p;
int POW = 1;
for (int i = 1; i <= qwq; i++) {
POW = (POW * Pow) % p;
if (mp.count(POW)) return i * qwq - mp[POW];
}
return -1;
}
```
::::
## 扩展大步小步算法 / exBSGS
~~不像前面几个,exBSGS 和 BSGS 终于有关系了~~
> 给定三个整数 $a, b, p$,求一个最小的非负整数 $x$,使得 $a^x \equiv b \pmod p$。
和 BSGS 相比,$a$ 和 $p$ 不一定互质,怎么办?
我们注意到 $a^x \equiv b \pmod p$ 等价于
$$
a^x + kp = b \ (k \in \mathbb{Z})
$$
我们令 $d = \gcd(a, p)$,根据裴蜀定理,只有 $d \mid b$ 才有解,$d \nmid b$ 无解。
如果 $d \mid b$,我们将等式两边同时除以 $d$。
$$
\begin{aligned}
& a^x + kp = b \\
& \Leftrightarrow \frac{a}{d} a^{x - 1} + k \frac{p}{d} = \frac{b}{d} \\
& \Leftrightarrow \frac{a}{d} a^{x - 1} \equiv \frac{b}{d} \pmod {\frac{p}{d}} \\
& \Leftrightarrow a^{x - 1} \equiv \frac{b}{d} \times \left(\frac{a}{d}\right)^{-1} \pmod {\frac{p}{d}}
\end{aligned}
$$
我们成功实现了降次。如果 $a$ 和 $\frac{p}{d}$ 已经互质了,可以直接 BSGS,如果没有,继续降次即可。
::::success[code]
```cpp
int BSGS() {
int qwq = sqrt(p);
if (qwq * qwq != p) qwq++;
mp[n] = 0;
for (int i = 1; i <= qwq; i++) {
n = (n * b) % p;
mp[n] = i;
}
int Pow = 1;
for (int i = 1; i <= qwq; i++) Pow = (Pow * b) % p;
int POW = 1;
for (int i = 1; i <= qwq; i++) {
POW = (POW * Pow) % p;
if (mp.count(POW)) return i * qwq - mp[POW];
}
return -1;
}
int solve() {
if (b == 1 || p == 1) return 0;
int sum = 0, real_a = 1;
while (114514) {
int d = __gcd(a, p);
if (d == 1) break;
if (b % d) return -1;
sum++;
b /= d, p /= d;
real_a = real_a * (a / d) % p;
if (real_a == b) return sum;
}
int ans = BSGS(real_a);
if (ans == -1) return -1;
else return ans + sum;
}
```
::::