POJ 3414 BFS / 模拟法

大致题意

有两个容量分别为A升和B升的罐子,通过3类操作使其中一个罐子恰好装入C升的水。操作分别是:将其中一个罐子装满水;将其中一个罐子里的水全部倒入水槽;其中一个罐子向另一个罐子倒入水,该操作分两种情况(倒入水的罐子装满,或倒出水的罐子将水倒完)。
输出最小操作数,及符合最小操作数的任一操作序列;无法实现输出 “impossible”。A、B、C范围为1~100,且C<=max(A, B)。

方法一:BFS + map实现的路径保存

BFS搜索A、B装入不同升水的状态,状态数不超过1e4。本来想用数组记录路径,然后在Discuss里面看到了一个很有意思的写法:把记录状态的结构体作为map的key,就试着写了一下。用结构体指针作为key可以减少内存占用。

一般而言,BFS队列空了仍未搜索到就判无法达到目标状态。这里在BFS前用最大公约数(gcd)即可做判定。因为由裴蜀定理可知,对于任意的整数x, y,其gcd(a, b)是(ax + by)的最小正值,(ax + by)的值都一定是gcd(a, b)的倍数。易证,罐子内水的体积数是A, B的线性组合。

#include <map>
#include <iostream>
#include <queue>
#include <string>
#define min(a,b)    (((a) < (b)) ? (a) : (b))
#define max(a,b)    (((a) > (b)) ? (a) : (b))
#define abs(x)    ((x) <0 ? -(x): x)
#define INF 0x3f3f3f3f
#define eps 1e-4
#define M_PI 3.14159265358979323846
#define MAX_N 100005
using namespace std;
struct state{
	int a, b;
	string s;
	//map实现结构体作为key
	//需要重载operator < 运算符函数,使其内部有序
	bool operator < (const state& other)const{
		if(a != other.a) return a < other.a;
		return b < other.b;
	}
	state(int a, int b, string s) : a(a), b(b), s(s) {}
	state(){}
};
int A, B, C, K;
map<state, state> M;
queue<state> Q;
int gcd(int a, int b){
	if(b == 0) return a;
	return gcd(b, a % b);
}
void next(int a, int b, string s, state now){
	state nxt = state(a, b, s);
	if(M.find(nxt) == M.end()){
		Q.push(nxt);
		M[nxt] = now;
	}
}
int main(){
	cin >> A >> B >> C;
	if(C % gcd(A, B)) printf("impossible\n");
	state now = state(0, 0, "");
	Q.push(now);
	while(!Q.empty()){
		now = Q.front(); Q.pop();
		if(now.a == C || now.b == C) break;
		//状态转移
		next(A, now.b, "FILL(1)", now);
		next(now.a, B, "FILL(2)", now);
		next(0, now.b, "DROP(1)", now);
		next(now.a, 0, "DROP(2)", now);
		next(max(now.a + now.b - B, 0), min(B, now.b + now.a), "POUR(1,2)", now);
		next(min(A, now.a + now.b), max(now.b + now.a - A, 0), "POUR(2,1)", now);
	
	}
	string res = "";
	while(now.a || now.b){
		res = now.s + "\n" + res;
		now = M[now];
		++K;
	}
	cout << K << endl << res;
	return 0;
}
方法二:模拟法

根据罐子内水的体积数是A, B的线性组合这一性质,且根据题意C<=max(A, B) ,对于ax + by = c ,x, y 不能同时大于0。所以只可能向其中一个罐子倒满水(FILL),另一个倒或不倒掉水(DROP),就得到了模拟倒水过程的基本思路。

如果只考虑FILL和DROP两个操作,可直接求出满足题意ax + by = c的解。先用拓展欧几里德算法求出ax + by = gcd(a, b)的一组可行解,然后左右两边乘以c / gcd(a, b)即可得到ax + by = c的一组可行解。因为a与b/gcd(a, b)以及b与a/gcd(a, b)互质,所以配出如下包含a, b的等式。

∵ a x + b y + n a b / g c d ( a , b ) − n a b / g c d ( a , b ) = c \because ax+by+nab/gcd(a,b)-nab/gcd(a,b)=c ax+by+nab/gcd(a,b)nab/gcd(a,b)=c
∴ a [ x + n b / g c d ( a , b ) ] + b [ y − n a / g c d ( a , b ) ] = c \therefore a[x+nb/gcd(a,b)]+b[y-na/gcd(a,b)]=c a[x+nb/gcd(a,b)]+b[yna/gcd(a,b)]=c

大括号中的n取不同值代表了所有可能解。利用取余即可求出满足最小值的可能解。

#include <cstdio>
#include <STDLIB.H>
#include <string>
#include <iostream>
#define min(a,b)    (((a) < (b)) ? (a) : (b))
#define max(a,b)    (((a) > (b)) ? (a) : (b))
#define abs(x)    ((x) < 0 ? -(x) : (x))
#define INF 0x3f3f3f3f
#define eps 1e-4
#define M_PI 3.14159265358979323846
#define MAX_A 150015
#define MAX_M 50005
#define MAX_N 50005
using namespace std;
typedef pair<int, string> P;
int A, B, C;

int gcd(int a, int b){
	if(b == 0) return a;
	return gcd(b, a % b);
}
//模拟罐子操作过程
P op(int A, int B, string n1, string n2){
	string res = "", un = n1 + "," + n2;
	int k = 0, p1 = 0, p2 = 0;
	while(p1 != C && p2 != C){
		if(p1 == 0){
			p1 = A, ++k;
			res += "FILL(" + n1 + ")\n";
		}
		while(p2 < B && p1 > 0 && p1 != C && p2 != C){
			int dif = min(p1, B - p2);
			p2 += dif, p1 -= dif, ++k;
			res += "POUR(" + un + ")\n";
		}
		if(p2 == B && p1 != C && p2 != C){
			p2 = 0, ++k;
			res += "DROP(" + n2 + ")\n";
		}
	}
	return P(k, res);
}
int main(){
	cin >> A >> B >> C;
	if(C % gcd(A, B)) printf("impossible\n");
	else{
		string n1 = "1", n2 = "2";
		P p1 = op(A, B, n1, n2);
		P p2 = op(B, A, n2, n1);
		//从两个可能解中取操作数最小的解
		P res = p1.first < p2.first ? p1 : p2;
		cout << res.first << endl << res.second;
	}
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值