学习笔记:二元一次不定方程
设整数 \(k\ge2\),\(c,a_1,\cdots,a_k\) 为整数,且 \(a_1,\cdots,a_k\) 不为零,\(x_1,\cdots,x_k\) 为整数变量,则方程
称为 \(k\) 元一次不定方程,\(a_1,\cdots,a_k\) 称为 系数。
一次不定方程有解的条件
定理(Bézout) 不定方程 \((1)\) 有解的充分必要条件是 \(\gcd(a_1,\cdots,a_k)\mid c\)。
证明省略。
进而,设 \(g=\gcd(a_1,\cdots,a_k)\),则 \((1)\) 的解与
的解相同。
一次不定方程的求解:扩展 Euclid 算法(exgcd)
在算法竞赛中,最常用的是求 二元一次不定方程 \(ax+by=c\) 的解。
由 Bézout 定理,设 \(d=\gcd(a,b)\),则求出 \(ax+by=d\) 的解,再将 \(x,y\) 乘以 \(c/d\),就得到原方程的解。
下面讨论如何求出 \(ax+by=d\) 的解。
由下一小节的定理,求出一组特解 \(x_0,y_0\) 就可以求出所有解。
为了求出一组特解,采用 辗转相除法,将方程系数规模减小。
一般地,对于方程 \(ax+by=d\),由于 \(d=\gcd(a,b)=\gcd(b,a\bmod b)\),\(ax+by=d\) 有解等价于 \(bx'+(a\bmod b)y'=d\) 有解,其中由 \(a\bmod b=a-\lfloor a/b\rfloor b\) 可解出
故根据递归的思想,像这样不断减小系数的规模,直到 \(b=0\),此时 \(d=a,x=1,y=0\),再递归回去,由 \((b,a\bmod b)\) 对应的 \(x',y'\) 推出上一步 \((a,b)\) 对应的 \(x,y\)。代码如下:
void exgcd(int a, int b, int &x, int &y) {
if(!b) return x = 1, y = 0, void();
int d = exgcd(b, a % b, x, y);
int _y = x - (a / b) * y;
x = y, y = _y;
return;
}
或者:
void exgcd(int a, int b, int &x, int &y) {
if (!b) return x = 1, y = 0, void();
exgcd(b, a % b, y, x), y -= a / b * x;
}
时间复杂度为辗转相除法的复杂度 \(O(\log V)\),其中 \(V\) 为值域大小。
设 \(d=\gcd(a,b)\),可以证明 exgcd 算法求得的 \(ax+by=c\) 的解是 绝对值最小 的解,其范围满足 \(|x|\le|b/2d|\) 且 \(|y|\le|a/2d|\)。
二元一次不定方程解的结构
二元一次不定方程的通解
定理 设 \(x_0,y_0\) 是方程 \(ax+by=c\) 的一组解,则它的所有解为
不难验证上式给出的 \(x,y\) 是方程的解。证明所有 \(x,y\) 都能表示成上述形式的过程省略。
由此我们可以讨论二元一次不定方程解的结构。在实际问题中,往往非负解或正解才是有意义的。那么如何判断方程是否有非负解或正解呢?
二元一次不定方程的非负解与正解
设 \(a,b\) 为正整数。由于两边乘除 \((a,b)\),得到的解是完全相同的,我们不妨令 \((a,b)=1\),那么全部解形如
显然,\(t\) 越大,\(x\) 越大,\(y\) 越小。如果要限制 \(x\ge0\),则解得 \(t\ge\left\lceil\dfrac{-x_0}b\right\rceil\);同理,限制 \(b\ge0\),则解得 \(t\le\left\lfloor\dfrac{y_0}b\right\rfloor\)。
同理可求正解对应 \(t\) 的范围。限制 \(x>0\),则 \(t\ge\left\lceil\dfrac{-x_0+1}b\right\rceil\);限制 \(y>0\),则 \(t\le\left\lfloor\dfrac{y_0-1}a\right\rfloor\)。
二元一次不定方程非负解与正解的个数
首先,如果 \(a,b\) 异号,那么显然有无穷多组解。下面设 \(a,b\) 同号,我们有下面的定理。
定理 设 \(a,b,c\) 均为正整数,\((a,b)=1\),则:
- 当 \(c>ab-a-b\) 时,不定方程 \(ax+by=c\) 有非负解,解的个数等于 \(\left\lfloor\dfrac{x_0}b\right\rfloor+\left\lfloor\dfrac{y_0}a\right\rfloor+1\),也等于 \(\left\lfloor\dfrac c{ab}\right\rfloor\) 或 \(\left\lfloor\dfrac c{ab}\right\rfloor+1\);
- 当 \(c=ab-a-b\) 时,不定方程 \(ax+by=c\) 无非负解。
证明由上面求 \(t\) 的范围可推得,具体过程省略。
显然,要使 \(x=0\) 或 \(y=0\),必有 \(b\mid c\) 或 \(a\mid c\)。
下面讨论正解的个数。
定理 设 \(a,b,c\) 均为正整数,\((a,b)=1\),则:
- 当 \(c>ab\) 时,不定方程 \(ax+by=c\) 有正解,解的个数等于 \(\left\lceil\dfrac{x_0}b\right\rceil+\left\lceil\dfrac{y_0}a\right\rceil-1\),也等于 \(\left\lceil\dfrac c{ab}\right\rceil-1\) 或 \(\left\lceil\dfrac c{ab}\right\rceil\);
- 当 \(c=ab\) 时,不定方程 \(ax+by=c\) 无正解。
因此,只要求出一组特解 \(x_0,y_0\),就能求出非负解与正解的个数。
例题
洛谷-P5656 【模板】二元一次不定方程 (exgcd)
给定正整数 \(a,b,c\),对于不定方程 \(ax+by=c\):
- 若方程无整数解,输出 \(-1\);
- 若方程有整数解,但无正整数解,输出所有整数解中 \(x\) 的最小正整数值和 \(y\) 的最小正整数值;
- 若方程有正整数解,输出方程正整数解的数量、所有正整数解中 \(x\) 的最小值、\(y\) 的最小值、\(x\) 的最大值和 \(y\) 的最大值。
\(1\le T\le2\cdot10^5\),\(1\le a,b,c\le10^9\)。
注意 exgcd 算法求出的是 \(ax+by=d=\gcd(a,b)\) 的特解 \(x,y\),再乘 \(c/d\) 才是原方程的解。
先求出一组特解 \(x,y\) 之后,获得 \(x\) 的最小正整数解 \(x_\min\),其对应的是 \(y_\max\),容易求出。同理求得 \(y_\min\) 和对应的 \(x_\max\)。
如果 \(y_\max\le0\),说明无正整数解,输出 \(x_\min\) 和 \(y_\min\)。
否则,由通解公式可知,正整数解的数量为 \(N=(x_\max-x_\min)/(b/d)+1\)(加一的原因,类似于植树问题),输出 \(N,x_\min,y_\min,x_\max,y_\max\) 即可。
点击查看代码
#include <bits/stdc++.h>
#define int long long
using namespace std;
void exgcd(int a, int b, int &x, int &y) {
if (!b) return x = 1, y = 0, void();
exgcd(b, a % b, y, x), y -= a / b * x;
}
void solve() {
int a, b, c;
cin >> a >> b >> c;
int d = __gcd(a, b);
if (c % d != 0) return cout << "-1\n", void();
int x, y;
exgcd(a, b, x, y);
x *= c / d, y *= c / d;
int xmn = x % (b / d); // x_min
if (xmn <= 0) xmn += b / d;
int ymx = (c - a * xmn) / b; // x_min 对应 y_max
int ymn = y % (a / d); // y_min
if (ymn <= 0) ymn += a / d;
int xmx = (c - b * ymn) / a; // y_min 对应 x_max
if (ymx <= 0) {
cout << xmn << ' ' << ymn << '\n';
} else {
cout << (xmx - xmn) / (b / d) + 1 << ' ' << xmn << ' ' << ymn << ' ' << xmx << ' ' << ymx << '\n';
}
return;
}
signed main() {
cin.tie(0)->sync_with_stdio(false);
int tt = 1;
cin >> tt;
while (tt--) solve();
return 0;
}
Gym-104008E Draw a triangle
给定平面上三角形的两个顶点坐标 \((x_1,y_1),(x_2,y_2)\),求另外一个顶点坐标 \((x_3,y_3)\),使得三角形面积最小。
要求三个顶点的坐标都为整数。输出任意一个解即可。
\(1\le T\le50000\),\(-10^9\le x_1,y_1,x_2,y_2\le10^9\),\(-10^{18}\le x_3,y_3\le10^{18}\)。
\(x_1=x_2\) 或 \(y_1=y_2\) 的情况是平凡的,下面设 \(x_1\ne x_2\) 且 \(y_1\ne y_2\)。
不妨设 \(x_1<x_2\)。先将三角形的两个顶点平移到 \((0,0),(x,y)\),其中 \(x=x_2-x_1,y=y_2-y_1\)。
不妨设 \(y>0\),\(y<0\) 的情况将 \(|y|\) 代入下面步骤后令 \(v=-v\) 即可。
设对这个三角形求出的坐标为 \((u,v)\),则 \((x_3,y_3)=(u+x_1,v+y_1)\)。
由向量的知识可知,三角形的面积为 \(S=\dfrac12|(x,y)\times(u,v)|=\dfrac12|xu-yv|\),最小化 \(|xu-yv|\)。
由于 \(xu-yv=w\) 有整数解当且仅当 \(\gcd(x,y)\mid w\),故令 \(xu-yv=\gcd(x,y)\),用 exgcd 解出 \((u,v)\) 即可。
点击查看代码
#include <bits/stdc++.h>
#define fi first
#define se second
#define int long long
using namespace std;
typedef pair<int, int> pii;
void exgcd(int a, int b, int &x, int &y) {
if (!b) return x = 1, y = 0, void();
exgcd(b, a % b, y, x), y -= a / b * x;
}
pii calc(int x, int y) {
int u, v;
exgcd(x, -y, u, v); // xv - yu = gcd(x, y)
return {u, v};
}
void solve() {
int x1, y1, x2, y2;
cin >> x1 >> y1 >> x2 >> y2;
int x = x2 - x1, y = y2 - y1;
if (x == 0) {
cout << x1 + 1 << ' ' << y1 << '\n';
} else if (y == 0) {
cout << x1 << ' ' << y1 + 1 << '\n';
} else {
if (x1 > x2) swap(x1, x2), swap(y1, y2), x = -x, y = -y;
if (y > 0) {
pii res = calc(x, y);
cout << x1 + res.fi << ' ' << y1 + res.se << '\n';
} else {
pii res = calc(x, -y);
cout << x1 + res.fi << ' ' << y1 - res.se << '\n';
}
}
return;
}
signed main() {
cin.tie(0)->sync_with_stdio(false);
int tt = 1;
cin >> tt;
while (tt--) solve();
return 0;
}
参考资料
- 《初等数论》潘承洞、潘承彪(证明省略的部分参见这本书)
- 同余代数 - qAlex_Weiq - 博客园

浙公网安备 33010602011771号