先用 exgcd 判断 ax+by=c 是否有整数解,再把通解写成 x=x0+k·b/d, y=y0-k·a/d,通过不等式求出正整数解对应的 k 范围。
OJ: luogu
题目 ID: P5656
难度:普及+/提高
标签:数论
日期: 2026-06-20 05:36
题意
给定方程:
需要分情况输出:
- 若无整数解,输出
- 若有正整数解,输出正整数解个数,以及这些正整数解里
/ 的最小值和最大值 - 若有整数解但没有正整数解,输出所有整数解里
的最小正整数值、 的最小正整数值
思路
先看一个更直接的小数据暴力:
cpp
#include <bits/stdc++.h>
using namespace std;
using i64 = long long;
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int T;
cin >> T;
while (T--) {
i64 a, b, c;
cin >> a >> b >> c;
vector<pair<i64, i64> > all_sol;
for (i64 x = -200; x <= 200; x++) {
for (i64 y = -200; y <= 200; y++) {
if (a * x + b * y == c) {
all_sol.push_back(make_pair(x, y));
}
}
}
if (all_sol.empty()) {
cout << -1 << '\n';
continue;
}
vector<pair<i64, i64> > pos_sol;
i64 min_pos_x = (1LL << 60);
i64 min_pos_y = (1LL << 60);
for (size_t i = 0; i < all_sol.size(); i++) {
i64 x = all_sol[i].first;
i64 y = all_sol[i].second;
if (x > 0 && x < min_pos_x) {
min_pos_x = x;
}
if (y > 0 && y < min_pos_y) {
min_pos_y = y;
}
if (x > 0 && y > 0) {
pos_sol.push_back(all_sol[i]);
}
}
if (!pos_sol.empty()) {
sort(pos_sol.begin(), pos_sol.end());
i64 xmin = pos_sol.front().first;
i64 xmax = pos_sol.back().first;
i64 ymin = (1LL << 60), ymax = 0;
for (size_t i = 0; i < pos_sol.size(); i++) {
ymin = min(ymin, pos_sol[i].second);
ymax = max(ymax, pos_sol[i].second);
}
cout << pos_sol.size() << ' ' << xmin << ' ' << ymin << ' ' << xmax << ' ' << ymax << '\n';
}
else {
cout << min_pos_x << ' ' << min_pos_y << '\n';
}
}
return 0;
}暴力版在一个小范围里枚举所有
这样写能帮助理解输出含义,但显然不能处理大数据。
关键观察还是扩展欧几里得的标准结论:
若:
那么方程:
有整数解的充要条件是:
能整除
如果不能整除,直接输出
若能整除,先用 exgcd 求出:
再整体乘上
通解形式
设:
则所有整数解都可以写成:
这里
正整数解怎么求
若要求
于是就能得到:
的下界 的上界
如果下界不超过上界,就说明有正整数解。
这张图表达的就是这个过程:
flowchart LR A["通解:x=x0+k·step_x"] --> C["把 x>0, y>0 变成 k 的区间"] B["通解:y=y0-k·step_y"] --> C C --> D["区间非空 => 有正整数解"]
图里真正要看的,是"问题已经不再是求
没有正整数解时输出什么
若整数解存在,但正整数解区间为空,那么题目要求输出:
- 所有整数解中
的最小正整数值 - 所有整数解中
的最小正整数值
这其实就是:
在模 意义下的最小正剩余 在模 意义下的最小正剩余
代码
cpp
#include <bits/stdc++.h>
using namespace std;
using i64 = long long;
using i128 = __int128_t;
i64 exgcd(i64 a, i64 b, i64 &x, i64 &y) {
if (b == 0) {
x = 1;
y = 0;
return a;
}
i64 d = exgcd(b, a % b, y, x);
y -= a / b * x;
return d;
}
i64 floor_div(i128 a, i128 b) {
if (b < 0) {
a = -a;
b = -b;
}
if (a >= 0) {
return (i64) (a / b);
}
return (i64) (-((-a + b - 1) / b));
}
i64 ceil_div(i128 a, i128 b) {
if (b < 0) {
a = -a;
b = -b;
}
if (a >= 0) {
return (i64) ((a + b - 1) / b);
}
return (i64) (-((-a) / b));
}
i64 min_positive_residue(i64 x, i64 mod) {
i64 r = (x % mod + mod) % mod;
if (r == 0) {
r = mod;
}
return r;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int T;
cin >> T;
while (T--) {
i64 a, b, c;
cin >> a >> b >> c;
i64 x0, y0;
i64 d = exgcd(a, b, x0, y0);
if (c % d != 0) {
cout << -1 << '\n';
continue;
}
i64 mul = c / d;
i64 step_x = b / d;
i64 step_y = a / d;
i128 x = (i128) x0 * mul;
i128 y = (i128) y0 * mul;
// 通解:
// x = x0 + k * step_x
// y = y0 - k * step_y
i64 kl = ceil_div(1 - x, step_x);
i64 kr = floor_div(y - 1, step_y);
if (kl <= kr) {
i64 cnt = kr - kl + 1;
i64 xmin = (i64) (x + (i128) kl * step_x);
i64 xmax = (i64) (x + (i128) kr * step_x);
i64 ymax = (i64) (y - (i128) kl * step_y);
i64 ymin = (i64) (y - (i128) kr * step_y);
cout << cnt << ' ' << xmin << ' ' << ymin << ' ' << xmax << ' ' << ymax << '\n';
}
else {
i64 xmin = min_positive_residue((i64) x, step_x);
i64 ymin = min_positive_residue((i64) y, step_y);
cout << xmin << ' ' << ymin << '\n';
}
}
return 0;
}复杂度
每组数据主要是一次 exgcd 和若干常数次计算:
空间复杂度:
总结
这题最核心的不是 exgcd 本身,而是通解形式:
一旦把这个通解写出来,后面所有输出要求都只是围绕
一图流解析
这张图把本题的建模、关键转移、实现检查和训练方法压缩到一页,适合读完正文后复盘。
