文章目录
- 一、裴蜀定理
-
- [1. 裴蜀定理的内容](#1. 裴蜀定理的内容)
- [2. 裴祖定理的推论](#2. 裴祖定理的推论)
- [3.【模板】裴蜀定理 ⭐⭐](#3.【模板】裴蜀定理 ⭐⭐)
- 二、扩展欧几里得算法 (exgcd)
-
- [1. exgcd 解决的问题](#1. exgcd 解决的问题)
- [2. exgcd 的算法过程](#2. exgcd 的算法过程)
- [3.【模板】二元一次不定方程 (exgcd) ⭐⭐⭐⭐](#3.【模板】二元一次不定方程 (exgcd) ⭐⭐⭐⭐)
-
- [(1) 解题思路](#(1) 解题思路)
- [(2) 代码实现](#(2) 代码实现)
- [三、exgcd 与乘法逆元](#三、exgcd 与乘法逆元)
-
- [1. 一次同余方程](#1. 一次同余方程)
- [2. exgcd 求解一次同余方程](#2. exgcd 求解一次同余方程)
- [3. exgcd 求解乘法逆元](#3. exgcd 求解乘法逆元)
- [4. 【模板】同余方程(求逆元)⭐⭐⭐](#4. 【模板】同余方程(求逆元)⭐⭐⭐)
- [四、练习:青蛙的约会 ⭐⭐⭐](#四、练习:青蛙的约会 ⭐⭐⭐)
-
- [1. 解题思路](#1. 解题思路)
- [2. 代码实现](#2. 代码实现)
一、裴蜀定理
1. 裴蜀定理的内容
裴蜀定理 又称贝祖定理,它的内容是:
设 d d d 是整数 a , b a,b a,b 的最大公约数,则一定存在整数 x , y x, y x,y,使得 a x + b y = d ax+by=d ax+by=d。
通过这个定理,我们可以判断一个不定方程是否存在整数解。
注意 a , b a, b a,b 的正负是不影响结果的。因为 a , b a, b a,b 如果存在解,那么也 a , − b a, -b a,−b 或者 − a , b -a, b −a,b 也一定存在解,只不过是在原来解的基础上添上一个负号。
2. 裴祖定理的推论
- 设 d d d 是整数 a , b a,b a,b 的最大公约数,则一定存在整数 x , y x, y x,y,使得 a x + b y = n d ax+by=nd ax+by=nd。( n ∈ Z n\in Z n∈Z)
因此我们可以得出,对于一个不定方程 a x + b y = c ax + by = c ax+by=c,如果有 gcd ( a , b ) ∣ c \gcd(a, b) \mid c gcd(a,b)∣c,那么该不定方程一定有解。
- 一定存在整数 x 1 , x 2 , ⋯ , x k x_1, x_2,\cdots, x_k x1,x2,⋯,xk,使得方程 a 1 x 1 + a 2 x 2 + ⋯ + a k x k = gcd ( a 1 , a 2 , ⋯ , a k ) × n a_1x_1 + a_2x_2 + \cdots + a_kx_k = \gcd(a_1, a_2, \cdots, a_k)\times n a1x1+a2x2+⋯+akxk=gcd(a1,a2,⋯,ak)×n 成立。
3.【模板】裴蜀定理 ⭐⭐
【题目链接】
【题目描述】
给定一个包含 n n n 个元素的整数 序列 A A A,记作 A 1 , A 2 , A 3 , . . . , A n A_1,A_2,A_3,...,A_n A1,A2,A3,...,An。
求另一个包含 n n n 个元素的待定整数 序列 X X X,记 S = ∑ i = 1 n A i × X i S=\sum\limits_{i=1}^nA_i\times X_i S=i=1∑nAi×Xi,使得 S > 0 S>0 S>0 且 S S S 尽可能的小。
【输入格式】
第一行一个整数 n n n,表示序列元素个数。
第二行 n n n 个整数,表示序列 A A A。
【输出格式】
一行一个整数,表示 S > 0 S>0 S>0 的前提下 S S S 的最小值。
【示例一】
输入
2 4059 -1782输出
99
【说明/提示】
对于 100 % 100\% 100% 的数据, 1 ≤ n ≤ 20 1 \le n \le 20 1≤n≤20, ∣ A i ∣ ≤ 1 0 5 |A_i| \le 10^5 ∣Ai∣≤105,且 A A A 序列不全为 0 0 0。
根据裴蜀定理, 不定方程 S ∑ i = 1 n A i × X i S\sum\limits_{i=1}^nA_i\times X_i Si=1∑nAi×Xi 的解为 gcd ( A 1 , A 2 , ⋯ , A n ) × k \gcd(A_1, A_2, \cdots, A_n)\times k gcd(A1,A2,⋯,An)×k,要使得它的解大于零且尽可能的小,那么实际上就是求 gcd ( A 1 , A 2 , ⋯ , A n ) \gcd(A_1, A_2, \cdots, A_n) gcd(A1,A2,⋯,An)。
cpp
#include<iostream>
using namespace std;
// 欧几里得算法求最大公因数
int gcd(int a, int b)
{
return b == 0 ? a : gcd(b, a % b);
}
int main()
{
int n; cin >> n;
int ret; cin >> ret;
while(--n)
{
int x; cin >> x;
ret = gcd(ret, abs(x));
}
cout << ret << endl;
return 0;
}
二、扩展欧几里得算法 (exgcd)
1. exgcd 解决的问题
对于一个不定方程
a x + b y = gcd ( a , b ) ax+by=\gcd(a,b) ax+by=gcd(a,b)来说,由裴祖定理我们知道这个方程一定是有解的,那么我们去求一组非零整数解 x , y x,y x,y 的过程,就要用到扩展欧几里得算法。
2. exgcd 的算法过程
它的计算过程如下:
注:下面推导中得分式除法表示整除。
- 当 b = 0 b = 0 b=0 时,
a x + b y = a x ax + by = ax ax+by=ax,因此有一组解为 x = 1 , y = 0 x = 1, y = 0 x=1,y=0;
- 当 b ≠ 0 b \ne 0 b=0 时,
由欧几里得算法得: gcd ( a , b ) = gcd ( b , a m o d b ) \gcd(a, b) = \gcd(b, a \bmod b) gcd(a,b)=gcd(b,amodb),则
gcd ( a , b ) = a x + b y ⇓ gcd ( b , a m o d b ) = b x 1 + ( a m o d b ) y 1 = b x 1 + ( a − a b × b ) y 1 = a y 1 + b ( x 1 − a b y 1 ) \gcd(a,b) = ax+by \\ \Downarrow \\ \begin{aligned} \gcd(b, a \bmod b) &= bx_1 + (a \bmod b)y_1 \\ &=bx_1 + (a - \frac{a}{b}\times b)y_1 \\ &= ay_1 + b(x_1 - \frac{a}{b}y_1) \end{aligned} gcd(a,b)=ax+by⇓gcd(b,amodb)=bx1+(amodb)y1=bx1+(a−ba×b)y1=ay1+b(x1−bay1)等式左右应该相等,因此
x = y 1 , y = x 1 − a b y 1 x = y1, y = x1 - \frac{a}{b}y_1 x=y1,y=x1−bay1于是我们可以利用递归,先求出下一层得 x 1 , y 1 x_1, y_1 x1,y1,再求出当前的 x , y x, y x,y。
上述递归过程,可以求出一组特解: x 0 , y 0 x_0, y_0 x0,y0,之后我们可以构造出通解,通解为
{ x = x 0 + b gcd ( a , b ) × k y = y 0 − a gcd ( a , b ) × k \begin{cases} x = x_0 + \frac{b}{\gcd(a, b)}\times k \\ y = y_0 - \frac{a}{\gcd(a, b)}\times k \end{cases} {x=x0+gcd(a,b)b×ky=y0−gcd(a,b)a×k其中 k ∈ N + k\in N^+ k∈N+。
代码实现:
cpp
typedef long long LL;
// exgcd(扩展欧几里得算法) 的返回值和 gcd(欧几里得算法) 一样,都是返回 gcd(a, b)
// 不同的是 exgcd 还可以求出不定方程 ax + by = gcd(a, b) 的一组特解 x, y
// 由于 C++ 只能有一个返回值,所以我们要求的 x, y 需要传引用
LL exgcd(LL a, LL b, LL& x, LL& y)
{
if(b == 0)
{
x = 1, y = 0;
return a;
}
// b 不为 0
LL x1, y1, d;
// 递归求出 x1, y1
d = exgcd(b, a % b, x1, y1);
// 利用公式求出当前层的 x, y
x = y1;
y = x1 - a / b * y1;
return d;
}
时间复杂度与欧几里得算法一致,为 O ( log n ) O(\log n) O(logn)。
如果自己手算一个不定方程 a x + b y = gcd ( a , b ) ax+by=\gcd(a,b) ax+by=gcd(a,b) 的解,可以利用先写出欧几里得算法的过程,之后逆向推出 x x x 和 y y y,下面给出一个示例:

3.【模板】二元一次不定方程 (exgcd) ⭐⭐⭐⭐
【题目链接】
【题目描述】
给定不定方程
a x + b y = c ax+by=c ax+by=c
若该方程无整数解,输出 − 1 -1 −1。
若该方程有整数解,且有正整数解,则输出其正整数 解的数量,所有正整数 解中 x x x 的最小值,所有正整数 解中 y y y 的最小值,所有正整数 解中 x x x 的最大值,以及所有正整数 解中 y y y 的最大值。
若方程有整数解,但没有正整数解,你需要输出所有整数解 中 x x x 的最小正整数值, y y y 的最小正整数值。
正整数解即为 x , y x, y x,y 均为正整数的解, 0 \boldsymbol{0} 0 不是正整数 。
整数解即为 x , y x,y x,y 均为整数的解。
x x x 的最小正整数值即所有 x x x 为正整数的整数解中 x x x 的最小值, y y y 同理。
【输入格式】
第一行一个正整数 T T T,代表数据组数。
接下来 T T T 行,每行三个由空格隔开的正整数 a , b , c a, b, c a,b,c。
【输出格式】
T T T 行。
若该行对应的询问无整数解,一个数字 − 1 -1 −1。
若该行对应的询问有整数解但无正整数解,包含 2 2 2 个由空格隔开的数字,依次代表整数解中, x x x 的最小正整数值, y y y 的最小正整数值。
否则包含 5 5 5 个由空格隔开的数字,依次代表正整数解的数量,正整数解中, x x x 的最小值, y y y 的最小值, x x x 的最大值, y y y 的最大值。
读入输出量较大,注意使用较快的读入输出方式
【示例一】
输入
7 2 11 100 3 18 6 192 608 17 19 2 60817 11 45 14 19 19 810 98 76 5432输出
4 6 2 39 8 2 1 -1 1600 1 18 3199 30399 34 3 -1 2 12 7 50 56
【说明/提示】
对于 100 % 100\% 100% 的数据, 1 ≤ T ≤ 2 × 10 5 1 \le T \le 2 \times {10}^5 1≤T≤2×105, 1 ≤ a , b , c ≤ 10 9 1 \le a, b, c \le {10}^9 1≤a,b,c≤109。
(1) 解题思路
求解一个二元一次不定方程 a x + b y = c ax + by = c ax+by=c 的通解:
先利用扩展欧几里得算法求出 a x + b y = gcd ( a , b ) ax + by = \gcd(a, b) ax+by=gcd(a,b) 的一组特解 x 0 , y 0 x_0, y_0 x0,y0,以及 d = gcd ( a , b ) d = \gcd(a, b) d=gcd(a,b);
用裴蜀定理判断原方程是否有解:
- 如果 d ∤ c d \nmid c d∤c,则无解;
- 如果 d ∣ c d\mid c d∣c,则有解。
在有解的前提下:
- 原方程的一组特解为
x 1 = x 0 × c d y 1 = y 0 × c d x_1 = \frac{x_0\times c}{d}\\ y_1 = \frac{y_0\times c}{d} x1=dx0×cy1=dy0×c
- 通解为
x = x 1 + b gcd ( a , b ) × k , y = y 1 − a gcd ( a , b ) × k x = x_1 + \frac{b}{\gcd(a, b)}\times k,\\y = y_1 - \frac{a}{\gcd(a, b)}\times k x=x1+gcd(a,b)b×k,y=y1−gcd(a,b)a×k
当我们求出特解之后,由通解的形式不难发现,当 x x x 在增大的同时, y y y 在减小,所以当 x x x 为最小正整数的时候,此时如果 y y y 不是正整数,那么 x , y x, y x,y 永远不可能同时为正整数,即没有正整数解。那么我们先利用 "模加模" 求出 x x x 的最小正整数解,
cpp
// 特解为 x, gcd(a, b) = d
int k1 = b / d,
x = (x % k1 + k1) % k1; // 把 x 补成最小正整数
之后利用原方程 a x + b y = c ax + by = c ax+by=c 反解出 y y y,即 y = ( c − a x ) / b y = (c - ax)/b y=(c−ax)/b,判断 y y y 是否也为正整数即可判断原方程是否有正整数解。
(2) 代码实现
cpp
#include<iostream>
using namespace std;
typedef long long LL;
// 扩展欧几里得求 ax + by = gcd(a, b) 的特解
LL exgcd(LL a, LL b, LL& x, LL& y)
{
if(b == 0)
{
x = 1, y = 0;
return a;
}
LL x1, y1, d;
d = exgcd(b, a % b, x1, y1);
x = y1;
y = x1 - a / b * y1;
return d;
}
int main()
{
int t;
scanf("%d", &t);
LL a, b, c;
while(t--)
{
scanf("%lld %lld %lld", &a, &b, &c);
LL x, y;
LL d = exgcd(a, b, x, y);
// 如果 d 不能整除 c, 则方程无解,输出-1
if(c % d)
{
printf("-1\n");
continue;
}
// 求出 ax + by = c 的特解
x = c / d * x;
y = c / d * y;
LL k1 = b / d, k2 = a / d;
x = (x % k1 + k1) % k1; // 把 x 补成最小正整数
x = x == 0 ? k1 : x; // 注意此时 x 有可能为 0, 不是正整数,至少要为 k1
y = (c - a * x) / b; // 根据原方程反解出 y
LL max_x, max_y, min_x, min_y;
if(y > 0) // 有整数解且有正整数解
{
min_x = x, max_y = y;
y %= k2; // 把 y 求成最小正整数解
y = y == 0 ? k2 : y;
min_y = y;
max_x = (c - b * y) / a;
// 解的个数就是 x(或y) 的最大正整数解 - 最小正整数解 / k1(或k2) + 1
printf("%lld %lld %lld %lld %lld\n", (max_x - min_x) / k1 + 1, min_x, min_y, max_x, max_y);
}
else // 有整数解但没有正整数解
{
min_x = x;
y = (y % k2 + k2) % k2;
y = y == 0 ? k2 : y;
min_y = y;
printf("%lld %lld\n", min_x, min_y);
}
}
return 0;
}
三、exgcd 与乘法逆元
1. 一次同余方程
形如
a x ≡ b ( m o d m ) ax ≡ b(\bmod m) ax≡b(modm)的一个方程就是同余方程,由于 x x x 的最高次数为 1 1 1,所以这是一个一次同余方程。
2. exgcd 求解一次同余方程
一个 a x ≡ b ( m o d m ) ax ≡ b(\bmod m) ax≡b(modm) 这样的同余方程可以转换为 a x = y m + b ax = ym + b ax=ym+b,移项得 a x − m y = b ax - my = b ax−my=b;
由于正负号不影响结果,所以等价于求解 a x + m y = b ax + my = b ax+my=b 这一个不定方程;
由裴蜀定理,当 gcd ( a , m ) ∣ b \gcd(a, m)\mid b gcd(a,m)∣b 时,该方程有解;
所以,我们可以通过扩展欧几里得算法求出 a x + m y = gcd ( a , m ) ax + my = \gcd(a, m) ax+my=gcd(a,m) 得特解,进而判断是否有 gcd ( a , m ) ∣ b \gcd(a, m)\mid b gcd(a,m)∣b,如果成立,那么说明 a x + m y = b ax + my = b ax+my=b 有解,设用 exgcd 求得的 a x + m y = gcd ( a , m ) ax + my = \gcd(a, m) ax+my=gcd(a,m) 的特解为 x 0 , y 0 x_0, y_0 x0,y0,则原方程的特解为
x 1 = x 0 × b gcd ( a , m ) y 1 = y 0 × b gcd ( a , m ) x_1 = \frac{x_0\times b}{\gcd(a, m)}\\ y_1 = \frac{y_0\times b}{\gcd(a, m)} x1=gcd(a,m)x0×by1=gcd(a,m)y0×b
进而可以求出通解。
3. exgcd 求解乘法逆元
对于正整数 a a a 和 p p p,若有 a x ≡ 1 ( m o d p ) ax\equiv1\pmod p ax≡1(modp),那么把这个同余方程中的 x x x 的解叫做 a a a 模 p p p 的乘法逆元,简称逆元。
逆元可以通过费马小定理求,但是必须要求 p p p 是一个质数,但是如果 p p p 不是质数,那么我们可以用扩展欧几里得算法求解乘法逆元,不难发现,这本质上就是求解一个同余方程的过程。
原同余方程可以转化为 a x + p y = 1 ax + py = 1 ax+py=1 的形式,根据裴蜀定理,该不定方程要有解,必须满足 gcd ( a , p ) ∣ 1 \gcd(a, p) \mid 1 gcd(a,p)∣1,那么 gcd ( a , p ) \gcd(a, p) gcd(a,p) 必须等于 1 1 1,也就是说 a , p a, p a,p 必须互质。所以能用扩展欧几里得求解乘法逆元的条件就是 a , p a, p a,p 互质。
4. 【模板】同余方程(求逆元)⭐⭐⭐
【题目链接】

cpp
#include<iostream>
using namespace std;
typedef long long LL;
// 扩展欧几里得
LL exgcd(LL a, LL b, LL& x, LL& y)
{
if(b == 0)
{
x = 1, y = 0;
return a;
}
LL x1, y1, d;
d = exgcd(b, a % b, x1, y1);
x = y1;
y = x1 - a / b * y1;
return d;
}
int main()
{
int t;
cin >> t;
while(t--)
{
LL a, b;
cin >> a >> b;
// ax + by = 1
LL x, y;
int d = exgcd(a, b, x, y);
// 如果 a, p 不互质,那么一定没有解
if(d != 1) cout << -1 << endl;
else
{
LL k = b / d;
x = (x % k + k) % k; // 补成最小正整数解
x = x == 0 ? k : x;
cout << x << endl;
}
}
return 0;
}
四、练习:青蛙的约会 ⭐⭐⭐
【题目链接】
【题目描述】
两只青蛙在网上相识了,它们聊得很开心,于是觉得很有必要见一面。它们很高兴地发现它们住在同一条纬度线上,于是它们约定各自朝西跳,直到碰面为止。可是它们出发之前忘记了一件很重要的事情,既没有问清楚对方的特征,也没有约定见面的具体位置。不过青蛙们都是很乐观的,它们觉得只要一直朝着某个方向跳下去,总能碰到对方的。但是除非这两只青蛙在同一时间跳到同一点上,不然是永远都不可能碰面的。为了帮助这两只乐观的青蛙,你被要求写一个程序来判断这两只青蛙是否能够碰面,会在什么时候碰面。
我们把这两只青蛙分别叫做青蛙 A 和青蛙 B,并且规定纬度线上东经 0 0 0 度处为原点,由东往西为正方向,单位长度 1 1 1 米,这样我们就得到了一条首尾相接的数轴。设青蛙 A 的出发点坐标是 x x x,青蛙 B 的出发点坐标是 y y y。青蛙 A 一次能跳 m m m 米,青蛙 B 一次能跳 n n n 米,两只青蛙跳一次所花费的时间相同。纬度线总长 L L L 米。现在要你求出它们跳了几次以后才会碰面。
【输入格式】
输入只包括一行五个整数 x , y , m , n , L x,y,m,n,L x,y,m,n,L。
【输出格式】
输出碰面所需要的次数,如果永远不可能碰面则输出一行一个字符串
Impossible。
【示例一】
输入
1 2 3 4 5输出
4
【说明/提示】
对于 100 % 100\% 100% 的数据, 1 ≤ x , y , m , n ≤ 2 × 1 0 9 1 \le x, y, m, n \le 2 \times 10^9 1≤x,y,m,n≤2×109, x ≠ y x \ne y x=y, 1 ≤ L ≤ 2.1 × 1 0 9 1 \le L \le 2.1 \times 10^9 1≤L≤2.1×109。
1. 解题思路
设两个青蛙相遇时跳了 t t t 次,那么相对于原点来说,青蛙 A 跳的总路程就是 x + t m x + tm x+tm,青蛙 B 跳的总路程是 y + t n y + tn y+tn,且这两个总路程的差值刚好是纬度线总长度的 k k k 倍,即
x + t m − ( y + t n ) = k l x + tm - (y + tn) = kl x+tm−(y+tn)=kl
整理后得到
( m − n ) t − k l = y − x (m - n)t - kl = y - x (m−n)t−kl=y−x
这个式子其实就是一个不定方程 a x + b y = c ax + by = c ax+by=c,我们要求的 t t t 就是这里的 x x x。
上面的式子还可以看作一个同余方程,如下
( m − n ) t ≡ y − x ( m o d l ) (m - n)t\equiv y - x\pmod l (m−n)t≡y−x(modl)
如果 m − n < 0 m - n < 0 m−n<0 那么等价于求解
( n − m ) t ≡ x − y ( m o d l ) (n - m)t\equiv x - y\pmod l (n−m)t≡x−y(modl)
2. 代码实现
cpp
#include<iostream>
using namespace std;
typedef long long LL;
LL exgcd(LL a, LL b, LL& x, LL& y)
{
if(b == 0)
{
x = 1, y = 0;
return a;
}
LL x1, y1, d;
d = exgcd(b, a % b, x1, y1);
x = y1;
y = x1 - a / b * y1;
return d;
}
int main()
{
LL x, y, m, n, l;
cin >> x >> y >> m >> n >> l;
// 设跳 x0 次
LL a = m - n, b = l, c = y - x, x0, y0;
if(a < 0) // 处理负数
{
a = -a;
c = -c;
}
// a * x0 + b * y0 = c
LL d = exgcd(a, b, x0, y0);
if(c % d) cout << "Impossible" << endl;
else
{
x0 = c / d * x0;
LL k = b / d;
x0 = (x0 % k + k) % k;
cout << x0 << endl;
}
return 0;
}