算法竞赛入门经典 7-8 倒水问题 UVa10603(BFS/隐式图最短路)
题意:有三个容量a,b,c的无刻度杯子,其中前两个为空,第三个装满水。现让它们互相倒水(不能中途停下,只能在某个杯子的水倒空或接满时停下),最少需要倒多少水,能使其中一个杯子的水量达到d,如果无法达到d,就让某个杯中的水量d'小于且尽量接近d。输出最少倒水量和目标水量(d或d')。
如果问题变成"最少需要多少次",不难用bfs解决。
问题是"最小倒水量“,不妨先考虑一下怎么表示状态。
每个状态需要用四个量来表示(杯中的水量v0,v1,v2和目前的倒水量dist),根据题意模拟互相倒水的所有情况并更新状态。
直觉上,为了保证dist最小,可以把状态扔进以dist从小到大排序的单调队列里,此时每次取出的都很可能是当前dist对应的倒水量最少的状态, 用它更新dist对应的答案。直到结束。
为什么这样做是正确的?因为所有可能的状态构成了一个图,上述算法实际上是在状态图中跑了一遍dijkstra算法。
结束后从d开始往下找有解的目标水量输出即可。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 // by 刘汝佳 2 #include <bits/stdc++.h> 3 using namespace std; 4 5 struct Node{ 6 int v[3], dist; 7 bool operator < (const Node& rhs) const { 8 return dist > rhs.dist; 9 } 10 }; 11 12 const int maxn = 200 + 10; 13 int vis[maxn][maxn], cap[3], ans[maxn]; 14 15 void update_ans(const Node &u) { 16 for (int i = 0; i < 3; i++) { 17 int d = u.v[i]; 18 if (ans[d] < 0 || u.dist < ans[d]) ans[d] = u.dist; 19 } 20 } 21 22 23 void solve(int a, int b, int c, int d) { 24 cap[0] = a; cap[1] = b; cap[2] = c; 25 memset(vis, 0, sizeof(vis)); 26 memset(ans, -1, sizeof(ans)); 27 priority_queue<Node> q; 28 29 Node start; 30 start.dist = 0; 31 start.v[0] = start.v[1] = 0; start.v[2] = c; 32 q.push(start); 33 vis[0][0] = 1; 34 35 while (!q.empty()) { 36 37 Node u = q.top(); q.pop(); 38 update_ans(u); 39 if (ans[d] >= 0) break; 40 41 for (int i = 0; i < 3; i++) 42 for (int j = 0; j < 3; j++) if (i != j) { 43 if (u.v[i] == 0 || u.v[j] == cap[j]) continue; 44 int amount = min(cap[j], u.v[i] + u.v[j]) - u.v[j]; 45 Node u2; 46 memcpy(&u2, &u, sizeof(u)); 47 u2.dist = u.dist + amount; 48 u2.v[i] -= amount; 49 u2.v[j] += amount; 50 if (!vis[u2.v[0]][u2.v[1]]) { 51 vis[u2.v[0]][u2.v[1]] = 1; 52 q.push(u2); 53 } 54 } 55 } 56 while (d >= 0) { 57 if (ans[d] >= 0) { 58 printf("%d %d\n", ans[d], d); 59 return; 60 } 61 d--; 62 } 63 } 64 65 int main() { 66 int T, a, b, c, d; 67 cin >> T; 68 while (T--) { 69 cin >> a >> b >> c >> d; 70 solve(a, b, c, d); 71 } 72 return 0; 73 }