假定两个无刻度有容量水壶 A A A和 B B B,供水量不限。可以使用三种方法装水:
给一个水壶装水;
把一个水壶倒空;
从一个水壶倒进另一个水壶。
当从一个水壶倒进另一个水壶时,如果第一个水壶倒空,或者第二个水壶装满就不能再倒了。例如,一个水壶
A
A
A是5加仑和另一个水壶
B
B
B是6加仑,水量是8加仑,则从水壶
A
A
A倒进水壶B时,让水壶
B
B
B充满水而水壶
A
A
A剩3加仑水。
问题由3个参数:
C
a
Ca
Ca,
C
b
Cb
Cb,
N
N
N,分别表示水壶
A
A
A和
B
B
B的容量,目标水量
N
N
N。解决问题的目标是给出一系列倒水的步骤,使水壶中的水量恰好是
N
N
N。
本题是由一道有名的数学题的一般形式。对于这个问题,最基本的思路肯定是需要利用两个桶反复取水倒水,最后得到目标值。题目要求步骤最少且写出步骤,容易想到使用BFS来求解问题。接下来考虑状态转移。
因为水壶无刻度有容量,这意味着从水井取水但不把水壶装满是无意义的。所以有六个转移方向:
1.
1.
1.Fill
A
A
A
2.
2.
2.Fill
B
B
B
3.
3.
3.Empty
A
A
A
4.
4.
4.Empty
B
B
B
5.
5.
5.pour
A
B
AB
AB,用
A
A
A中水倒到
B
B
B中。
6.
6.
6.pour
B
A
BA
BA,用
B
B
B中水倒到
A
A
A中。
每达到一个状态,就执行
6
6
6种操作,并压入队列,记下这个状态后继续搜索。直到出现解后停止搜索并输出。
然而,因为每次都会有6个状态入队,所以说重复的做法有非常多。这是我们就需要记忆化搜索来进行简化搜索。我们使用一个二维数组
c
h
e
c
k
check
check来记录
A
,
B
A,B
A,B里有若干水所需要的步骤数,步骤数最小的才入队,简化步骤。
#include<bits/stdc++.h>
using namespace std;
struct node
{
int a,b;
int num;
string s="";
};
int k,A,B,ANS;
int check[1001][1001];
void bfs()
{
// cout<<"1"<<endl;
queue<node>q;
node x;
memset(check,999999,sizeof(check));
x.a=0;x.b=0;x.num=0;
q.push(x);
while(!q.empty())
{
node y=q.front();q.pop();
for(int i=1;i<=6;i++)
{
node z=y;
z.num++;
if(i==1){z.a=A;z.s=z.s+"1";}
if(i==2){z.b=B;z.s=z.s+"2";}
if(i==3){z.a=0;z.s=z.s+"3";}
if(i==4){z.b=0;z.s=z.s+"4";}
if(i==5)
{
if(z.b+z.a>A)
{
z.b=z.b-(A-z.a);
z.a=A;
}
else
{
z.a=z.b+z.a;
z.b=0;
}
z.s=z.s+"5";
}
if(i==6)
{
if(z.b+z.a>B)
{
z.a=z.a-(B-z.b);
z.b=B;
}
else
{
z.b=z.a+z.b;
z.a=0;
}
z.s=z.s+"6";
}
if(check[z.a][z.b]>z.num)
{
check[z.a][z.b]=z.num;
q.push(z);
}
// cout<<z.b<<endl;
if(z.b==ANS)
{
cout<<z.num<<" ";
for(int i=0;i<z.s.size();i++)cout<<z.s[i]<<" ";cout<<endl;
return ;
}
}
}
}
int main()
{
cin>>k;
for(int i=1;i<=k;i++)
{
cin>>A>>B>>ANS;
bfs();
}
return 0;
}