先用 exgcd 求 ax+by=-c 的一组特解,再把通解写成 x=x0+k·b/d, y=y0-k·a/d,把矩形范围限制都转成对 k 的区间约束,最后求区间交集大小。
OJ: luogu
题目 ID: P2833
难度:普及+/提高
标签:数论
日期: 2026-06-20 05:39
题意
给定:
以及矩形范围:
要求满足条件的整数解 (x, y) 有多少对。
思路
先看一个最直接的小数据暴力:
cpp
#include <bits/stdc++.h>
using namespace std;
using i64 = long long;
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
i64 a, b, c, x1, x2, y1, y2;
cin >> a >> b >> c >> x1 >> x2 >> y1 >> y2;
if (x1 > x2) {
swap(x1, x2);
}
if (y1 > y2) {
swap(y1, y2);
}
long long answer = 0;
for (i64 x = x1; x <= x2; x++) {
for (i64 y = y1; y <= y2; y++) {
if (a * x + b * y + c == 0) {
answer++;
}
}
}
cout << answer << '\n';
return 0;
}暴力版就是直接枚举矩形里的所有整数点,看它们是否满足方程。
这个思路很好理解,但区间很大时显然不能枚举。
先把方程改写成:
若 a,b 都不为 0,设:
那么方程有整数解的充要条件是:
d能整除
若无解,答案就是 0。
若有解,用 exgcd 求出一组特解 (x0, y0)。
所有整数解可以写成:
这里 k 是任意整数。
接下来关键就变成:
- 哪些
k会让x落在[x1, x2] - 哪些
k会让y落在[y1, y2]
只要分别把这两个条件转成对 k 的区间限制,再求交集大小即可。
这张图表示的就是这个过程:
flowchart LR A["通解:x=x0+k·b/d"] --> C["由 x 范围推出 k 区间"] B["通解:y=y0-k·a/d"] --> D["由 y 范围推出 k 区间"] C --> E["区间求交"] D --> E
图里真正要看的,是“二维平面里的点计数”已经被压成了“一维参数 k 的区间计数”。
另外还要单独处理几种退化情况:
因为这时通解形式会更简单,直接特判更稳。
代码
cpp
#include <bits/stdc++.h>
using namespace std;
using i64 = long long;
using i128 = __int128_t;
const i64 INF64 = (1LL << 60);
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));
}
bool add_constraint(i64 x0, i64 step, i64 L, i64 R, i64 &kl, i64 &kr) {
if (L > R) {
return false;
}
if (step == 0) {
return L <= x0 && x0 <= R;
}
i64 l, r;
if (step > 0) {
l = ceil_div((i128) L - x0, step);
r = floor_div((i128) R - x0, step);
}
else {
l = ceil_div((i128) R - x0, step);
r = floor_div((i128) L - x0, step);
}
kl = max(kl, l);
kr = min(kr, r);
return kl <= kr;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
i64 a, b, c, x1, x2, y1, y2;
cin >> a >> b >> c >> x1 >> x2 >> y1 >> y2;
if (x1 > x2) {
swap(x1, x2);
}
if (y1 > y2) {
swap(y1, y2);
}
i64 rhs = -c;
if (a == 0 && b == 0) {
if (rhs == 0) {
cout << (i64) ((i128) (x2 - x1 + 1) * (y2 - y1 + 1)) << '\n';
}
else {
cout << 0 << '\n';
}
return 0;
}
if (a == 0) {
if (rhs % b != 0) {
cout << 0 << '\n';
return 0;
}
i64 y = rhs / b;
if (y1 <= y && y <= y2) {
cout << (x2 - x1 + 1) << '\n';
}
else {
cout << 0 << '\n';
}
return 0;
}
if (b == 0) {
if (rhs % a != 0) {
cout << 0 << '\n';
return 0;
}
i64 x = rhs / a;
if (x1 <= x && x <= x2) {
cout << (y2 - y1 + 1) << '\n';
}
else {
cout << 0 << '\n';
}
return 0;
}
i64 x0, y0;
i64 d = exgcd(llabs(a), llabs(b), x0, y0);
if (a < 0) {
x0 = -x0;
}
if (b < 0) {
y0 = -y0;
}
if (rhs % d != 0) {
cout << 0 << '\n';
return 0;
}
i64 mul = rhs / d;
i64 base_x = (i128) x0 * mul;
i64 base_y = (i128) y0 * mul;
i64 step_x = b / d;
i64 step_y = -a / d;
i64 kl = -INF64, kr = INF64;
if (!add_constraint(base_x, step_x, x1, x2, kl, kr)) {
cout << 0 << '\n';
return 0;
}
if (!add_constraint(base_y, step_y, y1, y2, kl, kr)) {
cout << 0 << '\n';
return 0;
}
cout << (kr - kl + 1) << '\n';
return 0;
}复杂度
主过程是一遍 exgcd 加上常数次区间计算:
空间复杂度:
总结
这题的关键不是“矩形里数点”,而是先把方程的整数解写成通解。
一旦写出:
后面的问题就只剩下:
- 参数
k有多少个合法取值
一图流解析
这张图把本题的建模、关键转移、实现检查和训练方法压缩到一页,适合读完正文后复盘。
