【题目链接】
【题目描述】
有两个无刻度标志的水壶,分别可装 x 升和 y 升 ( x,y 为整数且均不大于 100 )的水。设另有一水 缸,可用来向水壶灌水或接从水壶中倒出的水, 两水壶间,水也可以相互倾倒。已知 x 升壶为空 壶, y 升壶为空壶。问如何通过倒水或灌水操作, 用最少步数能在x或y升的壶中量出 z ( z ≤ 100 )升的水 来。
【解题思路】
考虑三个容器内水量分别为$v_0$, $v_1$, $v_2$的状态$(v_0, v_1, v_2)$,我们可以通过枚举所有可能的倒水操作,扩展出该状态的后继,于是该题又化归为图上最短路的问题,因为总水量是一定的(将水缸的容量和初始设为一个足够大的数),并且都是整数,当$v_0$,$v_1$确定的时候,$v_2$也是确定的,也就是说我们可以仅通过$v_0$,$v_1$的值来确定一个状态,所以我们可以开一个二维数组来判重,这还是可以承受的。 如果是要求倒水步数最少,即为无权图最短路以BFS解决,如果要求倒水量最少,也可以加权搜索,即把一般队列换成优先队列,每次扩展倒水量最少的节点,同样可以解决,本题属于第一种情况。
【AC代码】
#include <cstdio>
#include <queue>
#include <cstring>
#define STATE_SIZE 3*sizeof(int)
#define MAXCAP 126
struct Node{
int volume[3];
int dist;
Node(int a, int b, int c){
volume[0] = a, volume[1] = b, volume[2] = c;
dist = 0;
}
Node(Node *father){
memcpy(volume, father->volume, STATE_SIZE);
dist = father->dist + 1;
}
};
int cap[3];
int target;
bool visted[MAXCAP][MAXCAP];
inline int BFS(){
std::queue<Node*> Q;
Node *s = new Node(0, 0, 2 * MAXCAP);
Q.push(s);
s->dist = 0;
visted[0][0] = true;
while(!Q.empty()){
Node *v = Q.front(); Q.pop();
if(v->volume[0] == target || v->volume[1] == target) return v->dist;
for(int i = 0; i < 3; i++){
for(int j = 0; j < 3; j++) if(i != j){
// 枚举可能的倒水方案,从i倒水到j
if(v->volume[i] == 0 || v->volume[j] == cap[j]) continue;
//i中无水或j中已满,均不合法
int amount = std::min(cap[j], v->volume[i] + v->volume[j]) - v->volume[j];
// 细节: 确定倒水量
Node *vi = new Node(v);
int &a = vi->volume[i], &b = vi->volume[j];
//技巧: 为比较长的变量名声明引用可以让代码更简洁清晰
a -= amount;
b += amount;
if(!visted[a][b]){
visted[a][b] = true;
Q.push(vi);
}
}
}
}
return -1;
}
int main(){
scanf("%d%d%d", cap, cap + 1, &target);
cap[2] = 2 * MAXCAP;
int ans = BFS();
if(ans != -1) printf("%d\n", ans);
else puts("impossible");
}
就是这样啦