程序设计思维 B - Pour Water(广度优先搜索)

题目

倒水问题 “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;
}

发布了12 篇原创文章 · 获赞 0 · 访问量 131

猜你喜欢

转载自blog.csdn.net/weixin_43826681/article/details/104688151
今日推荐