P1292 倒酒

题目描述


Winy 是一家酒吧的老板,他的酒吧提供两种体积的啤酒,\(a\) ml 和 \(b\) ml,分别使用容积为 \(a\) ml 和 \(b\) ml 的酒杯来装载。
酒吧的生意并不好。Winy 发现酒鬼们都非常穷。有时,他们会因为负担不起 \(a\) ml 或者 \(b\) ml 啤酒的消费,而不得不离去。因此,Winy 决定出售第三种体积的啤酒(较小体积的啤酒)。
Winy 只有两种杯子,容积分别为 \(a\) ml 和 \(b\) ml,而且啤酒杯是没有刻度的。他只能通过两种杯子和酒桶间的互相倾倒来得到新的体积的酒。

为了简化倒酒的步骤,Winy 规定:

  1. \(a \ge b\);
  2. 酒桶容积无限大,酒桶中酒的体积也是无限大(但远小于桶的容积);
  3. 只包含三种可能的倒酒操作:
  4. 将酒桶中的酒倒入容积为 \(b\) ml 的酒杯中;
  5. 将容积为 \(a\) ml 的酒杯中的酒倒入酒桶;
  6. 将容积为 \(b\) ml 的酒杯中的酒倒入容积为 \(a\) ml 的酒杯中。
  7. 每次倒酒必须把杯子倒满或把被倾倒的杯子倒空。

Winy 希望通过若干次倾倒得到容积为 \(a\) ml 酒杯中剩下的酒的体积尽可能小,他请求你帮助他设计倾倒的方案。

错误逻辑


  • 在\((a,b)\)的整系数线性组合问题中,其组合的结果一定是\(k\times gcd(a,b),k\in\mathbb{Z}\),而这道题目中\(a\) ml酒杯中要剩下的酒的体积尽可能小,也就是\(gcd(a,b)\)。
  • 根据扩展欧几里得算法,我们输入\((a,b)\)可以得到\(ax+by=gcd(a,b)\)中的\((x,y,d)\)。我原有的思路是对其中的\((x,y)\)都直接取绝对值来充当\((P_a,P_b)\),但只能过将近一半的样例,出错的原因就在于把逻辑想的太简单了。
#include <iostream>

int exgcd(int a, int b, int &x, int &y) {
    if (b == 0) {
        x = 1;
        y = 0;
        return a;
    }
    int d = exgcd(b, a % b, y, x);
    y -= a / b * x;
    return d;
}

int main() {
    int a = 0;
    int b = 0;
    scanf("%d %d", &a, &b);
    int x = 0;
    int y = 0;
    int minAlcohol = exgcd(a, b, x, y);
    // ax + by = d
    if (x < 0) x = -x;
    if (y < 0) y = -y;
    printf("%d\n", minAlcohol);
    printf("%d %d\n", x, y);
    return 0;
}
  • 题目中规定的三种倒酒操作,本质只有两个源头动作改变了杯中酒的总量:
    1. 往\(B\)杯中倒酒(总量为\(b\)),设操作次数为\(P_b\);
    2. 往\(A\)杯酒倒空(总量为\(a\)),设操作次数为\(P_a\)。
  • 最终我们要让\(A\)杯中剩下的酒量为\(d=gcd(a,b)\),我们实际求解的方程式:\(b\cdot P_b-a\cdot P_a=d\),而扩展欧几里得算法求解的是\(ax+by=d\)。将两式对应,可以发现\(P_a\)对应的是\(-x\);\(P_b\)对应的是\(y\)。直接对\((x,y)\)取绝对值在某些情况下会失效:
    例如输入是\((a=5,b=4)\),算出的特解是\(x=1,y=-1\)(即\(5\times1+4\times(-1)=1\)),如果直接取绝对值,变成了\(P_a=1,P_b=1\),但这会导致\(4\times1-5\times1=-1≠1\),显然是不合法的倒酒方案。

特解\(\rightarrow\)通解


  • 根据方程\(a(-P_a)+b(P_b)=d\),我们需要基于基础解\(x\)找到使得\(P_a\ge0\)且最小的解。二元一次方程的通解步长为\({b\over d}\),所以最小的非负\(P_a\)为:

    \[P_a\equiv (-x\pmod{{b\over d}}+{b\over d})\pmod{{b\over d}} \]

  • 在\(P_a\)最小的情况下,对应的\(P_b\)自然也是最小的,可以通过原方程直接求出:

    \[P_b={a\cdot P_a+d\over b} \]

#include <iostream>

int exgcd(int a, int b, int &x, int &y) {
    if (b == 0) {
        x = 1;
        y = 0;
        return a;
    }
    int d = exgcd(b, a % b, y, x);
    y -= a / b * x;
    return d;
}

int main() {
    int a = 0;
    int b = 0;
    scanf("%d %d", &a, &b);
    int x = 0;
    int y = 0;
    int minAlcohol = exgcd(a, b, x, y);
    // ax + by = d
    int step = b / minAlcohol;
    int Pa = ((-x % step) + step) % step;
    int Pb = (1LL * a * Pa + minAlcohol) / b;
    printf("%d\n", minAlcohol);
    printf("%d %d\n", Pa, Pb);
    return 0;
}
posted @ 2026-09-08 14:20  chen_xing  阅读(7)  评论(0)    收藏  举报