题目
倒水问题 “fill A” 表示倒满A杯,"empty A"表示倒空A杯,“pour A B” 表示把A的水倒到B杯并且把B杯倒满或A倒空。
Input
输入包含多组数据。每组数据输入 A, B, C 数据范围 0 < A <= B 、C <= B <= 1000 、A和B互质。
Output
你的程序的输出将由一系列的指令组成。这些输出行将导致任何一个罐子正好包含C单位的水。每组数据的最后一行输出应该是“success”。输出行从第1列开始,不应该有空行或任何尾随空格。
Sample Input
2 7 5
2 7 4
Sample Output
fill B
pour B A
success
fill A
pour A B
fill A
pour A B
success
Notes
如果你的输出与Sample Output不同,那没关系。对于某个"A B C"本题的答案是多解的,不能通过标准的文本对比来判定你程序的正确与否。 所以本题由 SPJ(Special Judge)程序来判定你写的代码是否正确。
思路
这道题是一道“隐图”题,可以用广度优先搜索来解决。
有图就要有点,笔者将节点(a, b)定义为某一状态下A杯中的水量为a、B杯中的水量为b。
回忆一下广度优先搜索的步骤:
首先将起始节点加入队列,并将起点标记为“被访问过”
接着进行下面的循环体:
①从队列中取出一个节点
②访问该点周围的未被访问过的节点
③将刚访问过的节点加入到队列中
其中,访问某一点周围的节点、循环体的退出条件需要视具体情况而定,记录一个点是否被访问过有很多种数据结构可以实现。
本题中,在每一种状态下,可以进行6种操作:
① fill A
② fill B
③ empty A
④ empty B
⑤ pour A -> B
⑥ pour B -> A
当然,有些操作还需要一些条件,比如pour A -> B的条件为A杯中不为空。
其中,pour A -> B和pour B -> A分别各有两种可能的情况,以pour A -> B为例:设操作前状态为(a, b),A杯的容量记为A,B杯的容量记为B,则操作后的状态有两种可能:
① (0, a + b) 条件:(a + b) <= B
② (a + b - B, B) 条件:(a + b) > B
同理,pour B -> A后的状态有两种可能:
① (a + b, 0) 条件:(a + b) <= A
② (A, a + b - A) 条件:(a + b) > A
以上的6种操作相当于某一状态访问其他状态的途径。在循环体中,当取出的状态(a, b)通过操作M访问到状态(a’, b’)后,将pair<(a’, b’), pair<(a, b), M>>添加到map中,然后将(a’, b’)标记为“已被访问过”。与上一题 A - Maze用二维布尔数组不同,在这题中,笔者用集合set来标记节点是否被访问过:如果一个节点在set中被找到,则说明该节点被访问过。
不过笔者在写这篇博客的时候又认为,自己应该还是用二维布尔数组来标记节点是否被访问过。例如,当输入A杯的容量为A、B杯的容量为B后,可以建立一个二维布尔数组visit[A+1][B+1],比如当visit[0][3] = true时,表明状态(0, 3)被访问过。
虽然使用set,将被访问过的节点加入set中的方法也能标记节点是否被访问过,但在set中查找的时间复杂度为O(n),而在二维数组中查找的时间复杂度仅为O(1)。
由于程序运行时会不断遇到重复的状态,故快速判断一个状态是否被访问过就十分有必要,故本题应该采用二维数组,用空间换取时间。
输出时,也是像之前一样利用map从终点回溯到起点的办法。与之前稍微不同的是,在回溯过程中,保留的是操作序列而不是节点序列。
代码
#include <iostream>
#include <vector>
#include <queue>
#include <map>
#include <set>
#include <utility>
#include <string>
using namespace std;
int A, B, g;
int fA, fB;
// 用来记录访问过的点
set<pair<int, int>> visit;
// 宽搜用的队列
queue<pair<int, int>> q;
// key = <<a_,b_>>, value = <method,<a,b>> 表示(a_,b_)由(a,b)使用method到达
map<pair<int, int>, pair<string, pair<int, int>>> mp;
// 打印用的向量
vector<string> v;
void clear()
{
fA = 0; fB = 0;
visit.clear();
while (!q.empty()) q.pop();
mp.clear();
v.clear();
return;
}
void printVector(vector<string> v)
{
for (vector<string>::iterator it = v.begin(); it != v.end(); it++) {
cout << *it << endl;
}
}
pair<int, int> operation(pair<int, int> u, int type)
{
int a = u.first;
int b = u.second;
switch (type)
{
case 1: // fill A
return make_pair(A, u.second);
case 2: // fill B
return make_pair(u.first, B);
case 3: // empty A
return make_pair(0, u.second);
case 4: // empty B
return make_pair(u.first, 0);
case 5: // pour A -> B
if ((a + b) <= B) return make_pair(0, a + b);
else return make_pair(a + b - B, B);
case 6: // pour B -> A
if ((a + b) <= A) return make_pair(a + b, 0);
else return make_pair(A, a + b - A);
default: // 报错
return make_pair(-1, -1);
}
}
string byMethod(int theMethod)
{
switch (theMethod)
{
case 1:
return string("fill A");
case 2:
return string("fill B");
case 3:
return string("empty A");
case 4:
return string("empty B");
case 5:
return string("pour A B");
case 6:
return string("pour B A");
default:
return string("Error");
}
}
void bfs(int goal)
{
q.push(make_pair(0, 0));
mp.insert(make_pair(make_pair(0, 0), make_pair("start", make_pair(0, 0))));
visit.insert(make_pair(0, 0));
while (!q.empty()) {
pair<int, int> u = q.front(); // 读取队列的第一个节点
q.pop();
// 开始搜索(x,y)周围的可达点
for (int i = 1; i <= 6; i++) {
pair<int, int> u_ = operation(u, i);
if (visit.find(u_) == visit.end()) { //判断是否已访问过
// u_未被访问过
visit.insert(u_);
q.push(u_);
string method = byMethod(i);
mp.insert(make_pair(u_, make_pair(method, u)));
if (u_.first == goal) { // 找到的点
fA = u_.first;
fB = u_.second;
goto found;
}
else if (u_.second == goal) {
fA = u_.first;
fB = u_.second;
goto found;
}
}
}
}
found:
pair<int, int> to = make_pair(fA, fB);
// map<pair<int, int>, pair<string, pair<int, int>>> mp;
pair<pair<int, int>, pair<string, pair<int, int>>> get = *mp.find(to);
pair<int, int> from = get.second.second;
string Method = get.second.first;
while (Method != string("start")) {
v.push_back(Method);
to = from;
get = *mp.find(to);
from = get.second.second;
Method = get.second.first;
}
reverse(v.begin(), v.end());
return;
}
int main()
{
while (cin >> A >> B >> g) {
clear();
bfs(g);
printVector(v);
cout << "success" << endl;
}
return 0;
}