大致题意
有两个容量分别为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[y−na/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;
}