题解:洛谷 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
【核心思想】
-
问题分析:给定三个容量分别为 \(A, B, C\) 的桶,初始状态为 \((0, 0, C)\)。每次操作可将一个桶的牛奶倒入另一个桶,直到倒满或倒空。要求找出所有 \(a = 0\)(\(A\) 桶为空)时,\(c\) 桶(\(C\) 桶)中牛奶可能的剩余量。这是一个状态空间搜索问题,关键在于用 DFS 枚举所有可达状态。
-
算法选择:
- 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\) 六种倒法
-
关键步骤:
- 初始化:读入 \(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) - 其余五种倒法同理
- 若 \(a + b > B\)(\(B\) 桶装不下):\(A\) 桶剩余 \(a - (B - b)\),\(B\) 桶满,即
- 若
- 收集答案:遍历所有状态,当
vis[0][j][i] == 1时,\(i\) 即为 \(A\) 桶为空时 \(C\) 桶的可能剩余量 - 输出:按升序输出所有可能的 \(c\) 值
-
时间/空间复杂度:
- 时间复杂度:\(O(A \cdot B \cdot C)\),状态空间大小为三个桶容量的乘积
- 空间复杂度:\(O(A \cdot B \cdot C)\),三维访问标记数组
-
状态空间搜索的核心思想:
- 状态定义:用三元组 \((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
浙公网安备 33010602011771号