1. 程式人生 > 其它 >演算法競賽入門經典 7-8 倒水問題 UVa10603(BFS/隱式圖最短路)

演算法競賽入門經典 7-8 倒水問題 UVa10603(BFS/隱式圖最短路)

題意:有三個容量a,b,c的無刻度杯子,其中前兩個為空,第三個裝滿水。現讓它們互相倒水(不能中途停下,只能在某個杯子的水倒空或接滿時停下),最少需要倒多少水,能使其中一個杯子的水量達到d,如果無法達到d,就讓某個杯中的水量d'小於且儘量接近d。輸出最少倒水量和目標水量(d或d')。

如果問題變成"最少需要多少次",不難用bfs解決。

問題是"最小倒水量“,不妨先考慮一下怎麼表示狀態。

每個狀態需要用四個量來表示(杯中的水量v0,v1,v2和目前的倒水量dist),根據題意模擬互相倒水的所有情況並更新狀態。

直覺上,為了保證dist最小,可以把狀態扔進以dist從小到大排序的單調佇列裡,此時每次取出的都很可能是當前dist對應的倒水量最少的狀態, 用它更新dist對應的答案。直到結束。

為什麼這樣做是正確的?因為所有可能的狀態構成了一個圖,上述演算法實際上是在狀態圖中跑了一遍dijkstra演算法。

結束後從d開始往下找有解的目標水量輸出即可。

 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 }
View Code