倒水问题(Fill, UVa 10603)

【题目描述】

有三个没有刻度的水壶,容量分别为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 }

代码量骤减。。

原文地址:https://www.cnblogs.com/mrclr/p/8391433.html