题解:洛谷 P1215 母亲的牛奶

【题目来源】

洛谷:P1215 [USACO1.4] 母亲的牛奶 Mother's Milk - 洛谷

【题目描述】

农民约翰有三个容量分别是 a,b,c 升的桶。

最初,a,b 桶都是空的,而 c 桶是装满牛奶的。有时,农民把牛奶从一个桶倒到另一个桶中,直到被灌桶装满或原桶空了。

当然每一次灌注都是完全的。由于节约,牛奶不会有丢失。

写一个程序去帮助农民找出当 a 桶是空的时候,c 桶中牛奶所剩量的所有可能性。

【输入】

单独的一行包括三个整数 a,b,c

【输出】

只有一行,升序地列出当 a 桶是空的时候,c 桶牛奶所剩量的所有可能性。

【输入样例】

8 9 10

【输出样例】

1 2 8 9 10

【核心思想】

  1. 问题分析:给定三个容量分别为 \(A, B, C\) 的桶,初始状态为 \((0, 0, C)\)。每次操作可将一个桶的牛奶倒入另一个桶,直到倒满或倒空。要求找出所有 \(a = 0\)\(A\) 桶为空)时,\(c\) 桶(\(C\) 桶)中牛奶可能的剩余量。这是一个状态空间搜索问题,关键在于用 DFS 枚举所有可达状态。

  2. 算法选择

    • DFS 深度优先搜索:从初始状态 \((0, 0, C)\) 出发,递归模拟六种倒牛奶操作
    • 三维访问标记:用 vis[a][b][c] 记录状态 \((a, b, c)\) 是否已访问,避免重复搜索和无限循环
    • 状态转移:对于当前状态 \((a, b, c)\),枚举将 \(A \to B\)\(A \to C\)\(B \to A\)\(B \to C\)\(C \to A\)\(C \to B\) 六种倒法
  3. 关键步骤

    • 初始化:读入 \(A, B, C\),初始状态为 \((0, 0, C)\)
    • DFS 搜索 dfs(a, b, c)
      • vis[a][b][c] 已访问,直接返回
      • 标记 vis[a][b][c] = 1
      • 六种倒法(以 \(A \to B\) 为例):
        • \(a + b > B\)\(B\) 桶装不下):\(A\) 桶剩余 \(a - (B - b)\)\(B\) 桶满,即 dfs(a-(B-b), B, c)
        • \(a + b \leq B\)\(B\) 桶能装下):\(A\) 桶空,\(B\) 桶变为 \(a + b\),即 dfs(0, a+b, c)
        • 其余五种倒法同理
    • 收集答案:遍历所有状态,当 vis[0][j][i] == 1 时,\(i\) 即为 \(A\) 桶为空时 \(C\) 桶的可能剩余量
    • 输出:按升序输出所有可能的 \(c\)
  4. 时间/空间复杂度

    • 时间复杂度:\(O(A \cdot B \cdot C)\),状态空间大小为三个桶容量的乘积
    • 空间复杂度:\(O(A \cdot B \cdot C)\),三维访问标记数组
  5. 状态空间搜索的核心思想

    • 状态定义:用三元组 \((a, b, c)\) 表示三个桶的当前牛奶量,满足 \(a + b + c = C\)(总量守恒)
    • 操作完备性:六种倒法覆盖了所有可能的合法操作,每种操作都是确定性的状态转移
    • 访问标记去重:牛奶总量有限,状态空间有限,标记已访问状态避免重复搜索和死循环
    • 条件收集:不关心路径,只关心所有可达状态中满足 \(a = 0\)\(c\) 值集合
    • 升序输出技巧:外层循环 \(i\)\(C\) 桶容量)从小到大,内层循环 \(j\)\(B\) 桶容量),保证输出升序
    • 适用于有限状态空间遍历、倒水问题、状态转移类问题

【解题思路】

【算法标签】

普及 #DFS-一维

【代码详解】

#include <bits/stdc++.h>
using namespace std;
int A, B, C;
bool vis[25][25][25];
void dfs(int a, int b, int c)
{
    if (vis[a][b][c]) return;  // 如果这三个桶的容积出现过就返回
    vis[a][b][c] = 1;  // 否则标这三个桶的容积组合为1
    if (a+b>B) dfs(a-(B-b), B, c);  // 把A倒入B,并继续搜索
    else dfs(0, a+b, c);
    if (a+c>C) dfs(a-(C-c), b, C);  // 把A倒入C,并继续搜索
    else dfs(0, b, a+c);
    if (b+a>A) dfs(A, b-(A-a), c);  // 把B倒入A,并继续搜索
    else dfs(a+b, 0, c);
    if (b+c>C) dfs(a, b-(C-c), C);  // 把B倒入C,并继续搜索
    else dfs(a, 0, b+c);
    if (c+a>A) dfs(A, b, c-(A-a));  // 把C倒入A,并继续搜索
    else dfs(a+c, b, 0);
    if (c+b>B) dfs(a, B, c-(B-b));  // 把C倒入B,并继续搜索
    else dfs(a, b+c, 0);
}
int main()
{
    cin >> A >> B >> C;  // 输入三个桶的容积
    dfs(0, 0, C);  // 开始dfs深搜
    for (int i=0; i<=C; i++) {  // 先遍历C桶再遍历B桶(反过来就是从小到大而是从大到小)
        for (int j=0; j<=B; j++) {
            if (vis[0][j][i]==1) {  // 输出A桶为0,且vis为1时,C桶的容积
                cout << i << " ";
            }
        }
    }
    cout << endl;
    return 0;
}

【运行结果】

8 9 10
1 2 8 9 10
posted @ 2026-08-03 08:46  团爸讲算法  阅读(15)  评论(0)    收藏  举报