倒水问题
作者:互联网
倒水问题
有三个杯子,容量分别为 $A,B,C$。
初始时,$C$ 杯装满了水,而 $A,B$ 杯都是空的。
现在在保证不会有漏水的情况下进行若干次如下操作:
将一个杯子 $x$ 中的水倒到另一个杯子 $y$ 中,当 $x$ 空了或者 $y$ 满了时就停止(满足其中一个条件才停下)。
请问,在操作全部结束后,$C$ 中的水量有多少种可能性。
输入格式
输入包含多组测试数据。
每组数据占一行,包含三个整数 $A,B,C$。
输出格式
每组数据输出一个结果,占一行。
数据范围
$0 \leq A,B,C \leq 4000$,
每个输入最多包含 $100$ 组数据。
输入样例:
0 5 5 2 2 4
输出样例:
2 3
解题思路
一开始直接写了个爆搜,结果写了半天还写错了,一共有六种倒水的方式,如果每种情况都手写一遍很麻烦而且很容易错,这里有个更为巧妙的方法,利用循环和数组来枚举这六种情况。
分析一下一共有多少种状态,每个杯子的容量最多是$4000$,因此三个杯子一共会有$4000^3$种不同的状态。但实际上每次倒水后必然至少有一个杯子是空的或者是满的,因此实际上的状态数为$2 \times 3 \times 4000^2$,这里的$2$是指空或满的情况,$3$是指从三个杯子中选一个是空或满的状态。当然这个是一个上限值,实际上的状态数是远远小于这个数的。
因此需要枚举每一个状态,从一个状态转移到另外一个状态,可以看作是图的遍历。
AC代码如下:
1 #include <bits/stdc++.h> 2 using namespace std; 3 4 typedef long long LL; 5 6 const LL B = 1000; 7 8 int a[3]; 9 unordered_set<LL> st; 10 unordered_set<int> ans; 11 12 LL get(int *b) { // 把三个杯子中的水(状态)映射成一个LL 13 return b[0] * B * B + b[1] * B + b[0]; 14 } 15 16 void dfs(int *b) { 17 if (st.count(get(b))) return; // 记录状态,避免重复搜索 18 st.insert(get(b)); 19 ans.insert(b[2]); 20 21 // 枚举所有的情况,从i倒水到j 22 for (int i = 0; i < 3; i++) { 23 for (int j = 0; j < 3; j++) { 24 if (i == j) continue; // 不可以自己倒水给自己 25 int t = min(b[i], a[j] - b[j]); // 取i杯子当前盛水量与j杯子剩余容量的最小值 26 int c[3] = {b[0], b[1], b[2]}; // 为了恢复现场开个临时数组 27 c[i] -= t, c[j] += t; // 倒水 28 dfs(c); 29 } 30 } 31 } 32 33 int main() { 34 while (~scanf("%d %d %d", a, a + 1, a + 2)) { 35 st.clear(); 36 ans.clear(); 37 int b[3] = {0, 0, a[2]}; 38 dfs(b); 39 printf("%d\n", ans.size()); 40 } 41 42 return 0; 43 }
参考资料
AcWing 3511. 倒水问题(暑假每日一题2022):https://www.acwing.com/video/4064/
标签:倒水,状态,int,问题,4000,ans,杯子 来源: https://www.cnblogs.com/onlyblues/p/16500822.html