https://cn.vjudge.net/problem/UVA-10603
题目大意:给出三个杯子的容量,开始时只有第三个杯子装满了水。给出目标容量d,求出最后要来回倒出多少升水可以使某个杯子的容量达到d。
分析:
假设在某个状态:第一个杯子有v0升水,第二个杯子有v1升水,第三个杯子有v2升水,则记该状态为(v0,v1,v2)。
把状态想象为结点,倒水的过程为线,则题目便转化为了图的问题,我们需要求初始状态到目标状态的最短距离。
但本题的目标是倒水量最少,不是步数最少,步数最少不一定倒水量最少!!
所以我们要改进一下算法,因为普通的bfs入队和出队对应的为步数,所以我们需要优先队列存储结点,在结点中重载运算符来定义排序。
还有一点要注意:题目有三个杯子不论怎么倒水都无法到达d升的情况,那么就让容量达到d'升,其中d<d',并且d'无限接近d。
代码:
#include <bits/stdc++.h>
using namespace std;
const int maxn = 210;
struct Node{
int water[3];
int dist;
bool operator < (const Node& node) const {
return dist>node.dist;
}
};
int vis[maxn][maxn], ans[maxn], full[3];
void update_ans(const Node cur) {
for(int i = 0; i < 3; i++) {
int w = cur.water[i];
if(ans[w]<0 || ans[w]>cur.dist)//不要忘记无法达到d升的情况
ans[w] = cur.dist;
}
}
void bfs(int a, int b, int c, int d) {
priority_queue<Node> q;
full[0] = a, full[1] = b, full[2] = c;
Node start;
start.water[0] = 0;
start.water[1] = 0;
start.water[2] = c;
start.dist = 0;
vis[start.water[0]][start.water[1]] = 1;
q.push(start);
while(!q.empty()) {
Node cur = q.top();q.pop();
update_ans(cur);
if(ans[d] >= 0) break;
for(int i = 0; i < 3; i++)
for(int j = 0; j < 3; j++) if(i!=j) {
if(cur.water[i]==0 || cur.water[j]==full[j])
continue;
//默认为i给j倒水,求倒出水的容量
int add = min(full[j], cur.water[i]+cur.water[j])-cur.water[j];
Node nt;
memcpy(&nt, &cur, sizeof(cur));
nt.dist = cur.dist + add;
nt.water[i] -= add;
nt.water[j] += add;
if(!vis[nt.water[0]][nt.water[1]]) {
q.push(nt);
vis[nt.water[0]][nt.water[1]] = 1;
}
}
}
while(d>=0) {//解决无法达到d升的情况
if(ans[d]>=0) {
cout << ans[d] << " " << d << endl;
return;
}
d--;
}
}
int main()
{
freopen("i.txt","r",stdin);
freopen("o.txt","w",stdout);
int n,a,b,c,d;
cin >> n;
while(n--) {
memset(vis, 0, sizeof(vis));
memset(ans, -1, sizeof(ans));
cin >> a >> b >> c >> d;
bfs(a,b,c,d);
}
return 0;
}