把每轮操作看成当前酒量加上 b 再对 a 取模,最小正体积就是 gcd(a,b),再用 exgcd 求 b·y-a·x=g 的最小正解。
OJ: luogu
题目 ID: P1292
难度:普及+/提高
标签:数论最大公约数思维
日期: 2026-06-20 06:42
题意
有两个杯子,容量分别是
只允许三种操作:
- 从酒桶把
杯倒满 - 把
杯倒空回酒桶 - 把
杯往 杯里倒,直到 空或 满
要求让
思路
先看一个直接按定义枚举“小数据倒几轮”的版本:
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;
cin >> a >> b;
i64 best_d = a + 1;
i64 best_x = -1;
i64 best_y = -1;
// 直接枚举“桶 -> B 杯”做多少次。
// 每做一次,等价于当前 A 杯里的酒量加上 b,再按 a 处理溢出。
for (i64 y = 1; y <= a; y++) {
i64 total = y * b;
i64 d = total % a;
if (d == 0) {
d = a;
}
i64 x = (total - d) / a;
if (d < best_d) {
best_d = d;
best_x = x;
best_y = y;
}
}
cout << best_d << '\n';
cout << best_x << ' ' << best_y << '\n';
return 0;
}这个朴素版没有真的去模拟所有细节,而是抓住了一个核心现象:
- 每做一轮“把 B 倒满,再尽量往 A 里倒”,本质上就是让
杯里的酒量加上一个
如果
所以这一整轮做完之后,
也就是说,能在
- …
最小正体积就是
因此问题变成:
- 在所有
里,最小正数是多少
这是非常经典的结论:
- 所有这样的值,恰好都是
的倍数 - 最小正值就是
所以第一行答案直接是:
怎么求第二行的 x y
设:
表示做了多少次 表示做了多少次
总共往系统里加入了
我们还希望
把式子改写成同余:
两边同时除以
这就变成了一个模意义下的逆元问题。
用扩展欧几里得求出
然后再反推:
代码
cpp
#include <bits/stdc++.h>
using namespace std;
using i64 = long long;
i64 a, b;
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;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin >> a >> b;
i64 x0, y0;
i64 g = exgcd(b, a, x0, y0);
i64 mod = a / g;
// y 表示“桶 -> B 杯”需要做多少次。
// 我们要找最小正整数 y,使得:
// b * y ≡ g (mod a)
i64 y = x0 % mod;
if (y < 0) {
y += mod;
}
if (y == 0) {
y = mod;
}
// x 表示“A 杯 -> 桶”需要做多少次。
i64 x = (b * y - g) / a;
cout << g << '\n';
cout << x << ' ' << y << '\n';
return 0;
}复杂度
主要就是一次
- 时间复杂度
- 空间复杂度
总结
这题看起来像模拟倒酒,但关键不在模拟细节,而在看出:
- 每一轮本质上是在做“加
后对 取模”
一旦把它翻译成同余,最小正体积就是
一图流解析
这张图把本题的建模、关键转移、实现检查和训练方法压缩到一页,适合读完正文后复盘。
