【题目描述】
有三个没有刻度的水壶,容量分别为a,b和c(单位为升,都是<=200的正整数)。初始时前两个水壶是空的,而第三个装满了水。每次可以从一个水壶往一个水壶里倒水,直到一个水壶倒空或者另一个水壶倒满。为了让某个水壶恰好有d升水,至少要倒多少升的水?如果无解,找一个小于且最接近d的d’代替d。
【输入输出】
输入
第一行一个整数 t ,代表有 t 组测试数据,接下来 t 行每行包括 a, b, c, d 四个整数,分别代表三个水壶的容量 a, b, c 和目标水量 d 。
输出
输出共 t 行,每一行包括两个整数,分别是最少的倒水量和目标水量(d 或者 d′)。
【分析】
假设某一时刻,第一个杯子中有v₁升水,第二个杯子中有v₂升水,第三个杯子中有v₃升水,称此时的状态为(v₁, v₂, v₃)。我们可以把“状态”想成途中的一个结点,通过如何倒水就可以实现状态的转化,那么就可以构成一个状态图。不过提到了图,不代表一定就要建图,因为每一个状态的倒水方式都有最多6种(3² - 3,不能自己往自己倒),那么完全可以求最图上最短路时现算出每一个结点的出边,这种不建图却用到了图论的方法称为隐式图。然后跑最短路的话这里采用 spfa。
不过先来谈谈建图的方法。对于每一个结点,它的出边可以算出来,那么就可以用 bfs 来建图,然后存图的话就可以用 vector。值得注意的是,三个水壶中的水永远是不变的,恒等于第三个水壶的容量,所以可以用前两个水壶中的水表示第三个水壶中的水,建图就可以从三维降低到二维。
1 #include<cstdio> 2 #include<iostream> 3 #include<cstring> 4 #include<cmath> 5 #include<algorithm> 6 #include<vector> 7 #include<queue> 8 using namespace std; 9 const int INF = 2147483647; 10 const int maxn = 205; 11 struct Cup //用结构体比较方便 12 { 13 int wa[3]; 14 }; 15 int wmax[3], d; //wmax三个杯子容量,d目标水量 16 bool exist[maxn][maxn]; //本来三维,可用二维表示,判断点是否存在 17 vector<Cup>v[maxn][maxn];//同理 18 vector<int>c[maxn][maxn]; 19 int daoshui(int a, int b, int amax, int bmax, int& newa, int& newb) //a往b倒 20 { 21 if(a >= bmax - b) //b被灌满 22 { 23 newa = a - (bmax - b); newb = bmax; return bmax - b; 24 } 25 else //a被倒空 26 { 27 newa = 0; newb = b + a; return a; 28 } 29 } 30 void init() 31 { 32 memset(exist, 0, sizeof(exist)); 33 for (int i = 0; i < maxn; i++) 34 for(int j = 0; j < maxn; ++j) 35 { 36 v[i][j].clear(); c[i][j].clear(); 37 } 38 return; 39 } 40 void build() //用bfs建图 41 { 42 init(); 43 exist[0][0] = 1; 44 queue<Cup>q; q.push((Cup){0, 0, wmax[2]}); 45 while(!q.empty()) 46 { 47 Cup now = q.front(); q.pop(); 48 for(int i = 0; i < 3; ++i) 49 for(int j = 0; j < 3; ++j) 50 { 51 if(i == j) continue; //不能自己往自己倒 52 int t[3]; Cup neww = now; 53 int cost = daoshui(now.wa[i], now.wa[j], wmax[i], wmax[j], t[i], t[j]); 54 neww.wa[i] = t[i]; neww.wa[j] = t[j]; 55 if(!exist[neww.wa[0]][neww.wa[1]]) //要进入的这一个状态没被访问过 56 { 57 /*在强调一下,之所以开二维,是因为已知前两个杯子里的水,就可以 58 求第三个,所以像exist这样的数组,永远是exist[x.wa[0][x.wa[1]], 59 而不可能出现exist[x.wa[2]][x.wa[]]。我刚开始wa就是因为写成了 60 exist[neww.wa[i]][neww.wa[j]]*/ 61 exist[neww.wa[0]][neww.wa[1]] = 1; 62 v[now.wa[0]][now.wa[1]].push_back(neww);//从now这个状态添加一条出边 63 c[now.wa[0]][now.wa[1]].push_back(cost); 64 q.push(neww); 65 } 66 } 67 } 68 69 } 70 int dis[maxn][maxn], vis[maxn][maxn]; 71 void spfa() 72 { 73 for(int i = 0; i < maxn; ++i) 74 for(int j = 0; j < maxn; ++j) 75 { 76 dis[i][j] = INF; vis[i][j] = 0; 77 } 78 queue<Cup>q; q.push((Cup){0, 0, wmax[2]}); //初始状态 79 dis[0][0] = 0; 80 while(!q.empty()) 81 { 82 Cup now = q.front(); q.pop(); 83 vis[now.wa[0]][now.wa[1]] = 0; 84 for(int i = 0; i < v[now.wa[0]][now.wa[1]].size(); ++i) 85 { 86 Cup neww = v[now.wa[0]][now.wa[1]][i]; 87 if(dis[now.wa[0]][now.wa[1]] + c[now.wa[0]][now.wa[1]][i] < dis[neww.wa[0]][neww.wa[1]]) 88 { // 有更好的结果就更新 89 dis[neww.wa[0]][neww.wa[1]] = dis[now.wa[0]][now.wa[1]] + c[now.wa[0]][now.wa[1]][i]; 90 if(!vis[neww.wa[0]][neww.wa[1]]) 91 { 92 vis[neww.wa[0]][neww.wa[1]] = 1; 93 q.push(neww); 94 } 95 } 96 } 97 } 98 int ans1 = 0, ans2 = 0; 99 for(int i = 0; i < maxn; ++i) 100 for(int j = 0; j < maxn; ++j) 101 { 102 if(exist[i][j]) //这个点存在 103 { 104 int newd = 0; 105 if(i <= d && i >= newd) newd = i; //不断更新d′,使 d′不断趋近于 d。 106 if(j <= d && j >= newd) newd = j; 107 int cwa = wmax[2] - i - j; 108 if(cwa <= d && cwa >= newd) newd = cwa; 109 if(newd > ans1 || (newd == ans1 && dis[i][j] < ans2)) 110 { 111 ans1 = newd; ans2 = dis[i][j]; 112 } 113 } 114 115 } 116 printf("%d %d ", ans2, ans1); 117 } 118 119 int main() 120 { 121 int t; scanf("%d", &t); 122 while(t--) 123 { 124 125 scanf("%d%d%d%d", &wmax[0], &wmax[1], &wmax[2], &d); 126 build(); 127 spfa(); 128 } 129 return 0; 130 }
代码不短,不过要是改成隐式图搜索,建图的函数几乎就可以全省了。
那么怎么改呢?想一想建图只不过就是求出了 vector<Cup>v[maxn][maxn] 和 vector<Cup>c[maxn][maxn],那么我们只要将 spfa中的 vector<Cup>v[maxn][maxn] 和 vector<Cup>c[maxn][maxn]替换成找出边和求边权的语句不就行了吗?
看看上面的代码,这个语句就是
1 int daoshui(int a, int b, int amax, int bmax, int& newa, int& newb) 2 { 3 if(a >= bmax - b) 4 { 5 newa = a - (bmax - b); newb = bmax; return bmax - b; 6 } 7 else 8 { 9 newa = 0; newb = b + a; return a; 10 } 11 } 12 13 for(int i = 0; i < 3; ++i) 14 for(int j = 0; j < 3; ++j) 15 { 16 if(i == j) continue; 17 int t[3]; Cup neww = now; 18 int cost = daoshui(now.wa[i], now.wa[j], wmax[i], wmax[j], t[i], t[j]); 19 }
那么就可以改了
1 #include <bits/stdc++.h> 2 using namespace std; 3 const int maxn = 205; 4 const int INF = 0x3f3f3f3f; 5 struct Point 6 { 7 int w[3]; 8 }; 9 int wmax[3], d; 10 int pour(int a, int b, int amax, int bmax, int& na, int& nb){ // a -> b 11 if (bmax - b >= a) {na = 0, nb = b + a; return a;} 12 else {na = a - (bmax - b), nb = bmax; return bmax - b;} 13 } 14 int dist[maxn][maxn]; 15 int vis[maxn][maxn]; 16 void spfa(){ 17 queue<Point>q; q.push((Point){0, 0, wmax[2]}); 18 for (int i = 0; i < maxn; i++) 19 for (int j = 0; j < maxn; j++) dist[i][j] = INF; 20 dist[0][0] = 0; 21 while (!q.empty()){ 22 Point now = q.front(); q.pop(); vis[now.w[0]][now.w[1]] = 0; 23 for (int i = 0; i < 3; i++){ 24 for (int j = 0; j < 3; j++){ 25 if (i == j) continue; 26 int t[3]; 27 Point np = now; 28 int cost = pour(np.w[i], np.w[j], wmax[i], wmax[j], t[i], t[j]); 29 np.w[i] = t[i], np.w[j] = t[j]; 30 if (dist[np.w[0]][np.w[1]] > dist[now.w[0]][now.w[1]] + cost){ 31 dist[np.w[0]][np.w[1]] = dist[now.w[0]][now.w[1]] + cost; 32 if (!vis[np.w[0]][np.w[1]]){ 33 vis[np.w[0]][np.w[1]] = 1; 34 q.push(np); 35 } 36 } 37 } 38 } 39 } 40 int ans1 = 0, ans2 = 0; 41 for (int i = 0; i < maxn; i++){ 42 for (int j = 0; j < maxn; j++){ 43 if (dist[i][j] < INF){ 44 int nd = 0; 45 if (i <= d && i >= nd) nd = i; 46 if (j <= d && j >= nd) nd = j; 47 int k = wmax[2] - i - j; 48 if (k <= d && k >= nd) nd = k; 49 if (nd > ans1 || (nd == ans1 && dist[i][j] < ans2)) {ans1 = nd; ans2 = dist[i][j];} 50 } 51 } 52 } 53 printf("%d %d ", ans2, ans1); 54 } 55 int main() 56 { 57 int t; scanf("%d", &t); 58 while(t--) 59 { 60 scanf("%d%d%d%d", &wmax[0], &wmax[1], &wmax[2], &d); 61 spfa(); 62 } 63 return 0; 64 }
代码量骤减。。