题目描述:
倒水问题 “fill A” 表示倒满A杯,"empty A"表示倒空A杯,“pour A B” 表示把A的水倒到B杯并且把B杯倒满或A倒空。
数据输入:
输入包含多组数据。每组数据输入 A, B, C 数据范围 0 < A <= B 、C <= B <=1000 、A和B互质。
Sample input:
2 7 5
2 7 4
数据输出:
你的程序的输出将由一系列的指令组成。这些输出行将导致任何一个罐子正好包含C单位的水。每组数据的最后一行输出应该是“success”。输出行从第1列开始,不应该有空行或任何尾随空格。
Sample output:
fill B
pour B A
success
fill A
pour A B
fill A
pour A B
success
特别的:如果你的输出与Sample Output不同,那没关系。对于某个"A B C"本题的答案是多解的,不能通过标准的文本对比来判定你程序的正确与否。 所以本题由 SPJ(Special Judge)程序来判定你写的代码是否正确。
个人思路:
对于倒水问题,实在BFS 中一类特殊的存在——隐式图问题。它只给出了初始节点;目标节点以及扩展的条件等约束条件则隐含给出。
对于本题:初始节点是杯子为空:a = 0,b=0;目标是量出C单位的水,这里我认为目标节点是:a = C || b = C;下面最关键的BFS主体,考虑到题目限制对杯子的操作只有 fill or empty,两个杯子之间有 atob or btoa;所以对于两只杯子一共只有六种操作,而这六种操作就是BFS扩展的条件,操作后的新状态就是new node,然后判断是否push入队列就ok了。
数据结构:
struct cup {//杯子的状态
int a, b, sign;//sign标记操作数{1,2,3,4,5,6}
bool operator<(const cup& p)const {
return a != p.a ? a < p.a : b < p.b;
}
bool operator == (const cup &q)const {
return a == q.a || b == q.b;
}
};
int A, B, C;//输入
cup s, t;//起终点
queue<cup> Q;//队列
map<cup, int> mp;//visit的作用
int mi = -1;//mp的指针
cup parent[100];//记录状态m的上一步操作parent[m]
在这里用的map来进行一个映射,首先是为了将cup类型转化为int型能够用于后面记录parent[mp[m]],这里注意到mi是mp的指针,理论上来说mp么得指针,至于它的具体作用在后面给出具体介绍。map的另一个作用在BFS中给出。
BFS
此BFS用mp.count(key)替代了以往visit[ ]的作用,用来作为判断push入队列的条件。
void BFS() {
s.a = s.b = 0;
s.sign = 0;
t.a = t.b = C;
int i = mi; //mp的指针
mp.clear();//清空mp
while (!Q.empty())//清空队列,因为多组数据可能会产生冲突
Q.pop();
Q.push(s);
mp[s] = ++i;//指针后移
cup final;
while (!Q.empty()) {
cup now = Q.front();
Q.pop();
if (now.a < A) {//fill A
cup n = now;
fillA(n);
if (mp.count(n) == 0) {//n不在mp中
n.sign = 1;//sign记录n是经过了什么操作
//cout << n.sign << endl;
Q.push(n);
mp[n] = ++i;//指针后移
parent[mp[n]] = now;//n的父节点是now,而mp[n]则是其指针
}
if (n == t) {//判定是否到达终点
final = n;
break;
}
}
if (now.b < B) {//fill B
cup n = now;
fillB(n);
if (mp.count(n) == 0) {//n不在mp中
n.sign = 2;//sign记录n是经过了什么操作
//cout << n.sign << endl;
Q.push(n);
mp[n] = ++i;//指针后移
parent[mp[n]] = now;//n的父节点是now,而mp[n]则是其指针
}
if (n == t) {//判定是否到达终点
final = n;
break;
}
}
if (now.a != 0 && now.b != 0) {//empty A or B
cup n1 = now;
emptyA(n1);
if (mp.count(n1) == 0) {//n1不在mp中
n1.sign = 3;//sign记录n1是经过了什么操作
//cout << n1.sign << endl;
Q.push(n1);
mp[n1] = ++i;//指针后移
parent[mp[n1]] = now;//n1的父节点是now,而mp[n1]则是其指针
}
if (n1 == t) {//判定是否到达终点
final = n1;
break;
}
cup n2 = now;
emptyB(n2);
if (mp.count(n2) == 0) {//n2不在mp中
n2.sign = 4;//sign记录n2是经过了什么操作
//cout << n2.sign << endl;
Q.push(n2);
mp[n2] = ++i;//指针后移
parent[mp[n2]] = now;//n2的父节点是now,而mp[n2]则是其指针
}
if (n2 == t) {//判定是否到达终点
final = n2;
break;
}
}
if (now.b != B && now.a != 0) {//pour A B
cup n = now;
AtoB(n);
if (mp.count(n) == 0) {//n不在mp中
n.sign = 5;//sign记录n是经过了什么操作
//cout << n.sign << endl;
Q.push(n);
mp[n] = ++i;//指针后移
parent[mp[n]] = now;//n的父节点是now,而mp[n]则是其指针
}
if (n == t) {//判定是否到达终点
final = n;
break;
}
}
if (now.a != A && now.b != 0) {//pour B A
cup n = now;
BtoA(n);
if (mp.count(n) == 0) {//n不在mp中
n.sign = 6;//sign记录n是经过了什么操作
//cout << n.sign << endl;
Q.push(n);
mp[n] = ++i;//指针后移
parent[mp[n]] = now;//n的父节点是now,而mp[n]则是其指针
}
if (n == t) {//判定是否到达终点
final = n;
break;
}
}
}
fprint(final);//打印操作
print(final);
cout << "success" << endl;
//cout << "( " << final.a << " , " << final.b << " )" << endl;
}
递归逆序输出到达终点的操作:
void print(const cup& n) {//根据sign标记输出操作
if (n.sign == 1)
cout << "fill A" << endl;
else if (n.sign == 2)
cout << "fill B" << endl;
else if (n.sign == 3)
cout << "empty A" << endl;
else if (n.sign == 4)
cout << "empty B" << endl;
else if (n.sign == 5)
cout << "pour A B" << endl;
else if (n.sign == 6)
cout << "pour B A" << endl;
else
return;
}
void fprint(cup& m) {//递归倒序输出操作
if (m.a == s.a && m.b == s.b)return;//出口
cup p = parent[mp[m]];
fprint(p);
print(p);
}
实现代码:
#include<iostream>
#include<queue>
#include<map>
using namespace std;
struct cup {//杯子的状态
int a, b, sign;//sign标记操作数{1,2,3,4,5,6}
bool operator<(const cup& p)const {
return a != p.a ? a < p.a : b < p.b;
}
bool operator == (const cup& q)const {
return a == q.a || b == q.b;
}
};
int A, B, C;//输入
cup s, t;//起终点
queue<cup> Q;//队列
map<cup, int> mp;//visit的作用
int mi = -1;//mp的指针
cup parent[100];//记录状态m的上一步操作parent[m]
//六种操作
void fillA(cup& m) {
m.a = A;
}
void fillB(cup& m) {
m.b = B;
}
void emptyA(cup& m) {
m.a = 0;
}
void emptyB(cup& m) {
m.b = 0;
}
void AtoB(cup& m) {
if (m.a >= B - m.b) {
int t = m.b;
m.b = B;
m.a = m.a - (B - t);
}
else {
m.b = m.b + m.a;
m.a = 0;
}
}
void BtoA(cup& m) {
if (m.b >= A - m.a) {
int t = m.a;
m.a = A;
m.b = m.b - (A - t);
}
else {
m.a = m.b + m.a;
m.b = 0;
}
}
void print(const cup& n) {//根据sign标记输出操作
if (n.sign == 1)
cout << "fill A" << endl;
else if (n.sign == 2)
cout << "fill B" << endl;
else if (n.sign == 3)
cout << "empty A" << endl;
else if (n.sign == 4)
cout << "empty B" << endl;
else if (n.sign == 5)
cout << "pour A B" << endl;
else if (n.sign == 6)
cout << "pour B A" << endl;
else
return;
}
void fprint(cup& m) {//递归倒序输出操作
if (m.a == s.a && m.b == s.b)return;//出口
cup p = parent[mp[m]];
fprint(p);
print(p);
}
void BFS() {
s.a = s.b = 0;
s.sign = 0;
t.a = t.b = C;
int i = mi; //mp的指针
mp.clear();//清空mp
while (!Q.empty())//清空队列,因为多组数据可能会产生冲突
Q.pop();
Q.push(s);
mp[s] = ++i;//指针后移
cup final;
while (!Q.empty()) {
cup now = Q.front();
Q.pop();
if (now.a < A) {//fill A
cup n = now;
fillA(n);
if (mp.count(n) == 0) {//n不在mp中
n.sign = 1;//sign记录n是经过了什么操作
//cout << n.sign << endl;
Q.push(n);
mp[n] = ++i;//指针后移
parent[mp[n]] = now;//n的父节点是now,而mp[n]则是其指针
}
if (n == t) {//判定是否到达终点
final = n;
break;
}
}
if (now.b < B) {//fill B
cup n = now;
fillB(n);
if (mp.count(n) == 0) {//n不在mp中
n.sign = 2;//sign记录n是经过了什么操作
//cout << n.sign << endl;
Q.push(n);
mp[n] = ++i;//指针后移
parent[mp[n]] = now;//n的父节点是now,而mp[n]则是其指针
}
if (n == t) {//判定是否到达终点
final = n;
break;
}
}
if (now.a != 0 && now.b != 0) {//empty A or B
cup n1 = now;
emptyA(n1);
if (mp.count(n1) == 0) {//n1不在mp中
n1.sign = 3;//sign记录n1是经过了什么操作
//cout << n1.sign << endl;
Q.push(n1);
mp[n1] = ++i;//指针后移
parent[mp[n1]] = now;//n1的父节点是now,而mp[n1]则是其指针
}
if (n1 == t) {//判定是否到达终点
final = n1;
break;
}
cup n2 = now;
emptyB(n2);
if (mp.count(n2) == 0) {//n2不在mp中
n2.sign = 4;//sign记录n2是经过了什么操作
//cout << n2.sign << endl;
Q.push(n2);
mp[n2] = ++i;//指针后移
parent[mp[n2]] = now;//n2的父节点是now,而mp[n2]则是其指针
}
if (n2 == t) {//判定是否到达终点
final = n2;
break;
}
}
if (now.b != B && now.a != 0) {//pour A B
cup n = now;
AtoB(n);
if (mp.count(n) == 0) {//n不在mp中
n.sign = 5;//sign记录n是经过了什么操作
//cout << n.sign << endl;
Q.push(n);
mp[n] = ++i;//指针后移
parent[mp[n]] = now;//n的父节点是now,而mp[n]则是其指针
}
if (n == t) {//判定是否到达终点
final = n;
break;
}
}
if (now.a != A && now.b != 0) {//pour B A
cup n = now;
BtoA(n);
if (mp.count(n) == 0) {//n不在mp中
n.sign = 6;//sign记录n是经过了什么操作
//cout << n.sign << endl;
Q.push(n);
mp[n] = ++i;//指针后移
parent[mp[n]] = now;//n的父节点是now,而mp[n]则是其指针
}
if (n == t) {//判定是否到达终点
final = n;
break;
}
}
}
fprint(final);//打印操作
print(final);
cout << "success" << endl;
//cout << "( " << final.a << " , " << final.b << " )" << endl;
}
int main() {
while (~scanf_s("%d%d%d", &A, &B, &C)) {//VS中是scanf_s,和scanf效果相同
BFS();
}
return 0;
}