其他分享
首页 > 其他分享> > 倒水问题

倒水问题

作者:互联网

倒水问题

有三个杯子,容量分别为 $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