题意:
体积为S的可乐,通过体积为N和M的杯子平分。判断是否可行,若能输出转倒次数,若不能输出NO。
解题思路:
1.一开始不容易想到宽搜,但是可以这样思考:每次倒可乐有六种选择(从一个倒入另一个,三个杯子。),所以会产生六个分支,决策不断进行下去,就形成了一个图。这个图的每个节点,是三个杯子装可乐的情况,我们可以用结构体来表示。题目要求的转倒次数,可以理解成从起始状态(S,0,0)发展到目标状态(S/2,S/2,0)的最短路径长度。这样就能看出宽搜的模型啦。
2.进一步思考,每次产生六个分支,为什么不是形成一棵树呢?这就要考虑杯子的实际情况了。杯子是有限的,不断倒出,倒入,存在某些情况下,三个杯子的状态会出现第二次。所以这道题的情境不满足树的结构。也从这里我们观察到,如果第二次出现一个状态,就形成一个回路,有其二必有其N,这是需要避免的。这就可以联想到搜索时需要设置的访问标记,这里笔者用三维数组vis[a][b][c]来实现。
3.bfs的框架看出来了,那如何实现“选择不同倒法”呢?这里我们可以写一个函数模拟倒可乐的过程,从i杯倒入j杯,考虑两种情况:总量超过j杯,或者不超过j杯。剩下的只需要循环遍历i和j的序号就可以了。
参考代码:
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 105;
bool vis[MAXN][MAXN][MAXN];//表示状态是否被访问
int S,N,M,V[5],ans;//V表示对应三个杯子的容积
typedef struct
{
int v[5];//v表示当前状态三个杯子的装容情况
int step;//记步求答案
}node;
node temp;
queue<node> q;
void pour(int i,int j)
{
int sum = temp.v[i]+temp.v[j];
if(sum>V[j])
{//如果j杯小了,就要在i杯
temp.v[i]=sum-V[j];
temp.v[j]=V[j];
}
else
{//如果j杯足够大,就倒完
temp.v[i]=0;
temp.v[j]=sum;
}
}
void bfs()
{
while(!q.empty())q.pop();
node t;
//初始状态
t.v[1]=V[1],t.v[2]=0,t.v[3]=0;
vis[t.v[1]][t.v[2]][t.v[3]]=true;
t.step=0;
q.push(t);
while(!q.empty())
{
t=q.front();
q.pop();
//结束条件
if(t.v[1]==t.v[2]&&t.v[3]==0)
{
ans=t.step;
break;
}
for(int i=1;i<=3;i++)
for(int j=1;j<=3;j++)
{
//九种情况里排除掉自己倒给自己的三种情况
if(i==j)continue;
temp=t;
pour(i,j);
//判断是否被访问过
if(vis[temp.v[1]][temp.v[2]][temp.v[3]])continue;
vis[temp.v[1]][temp.v[2]][temp.v[3]]=true;
temp.step++;
q.push(temp);
}
}
}
int main()
{
while(scanf("%d%d%d",&S,&N,&M)!=EOF&&N+M+S)
{
ans=0;
memset(vis,0,sizeof(vis));
if(N<M)swap(N,M);
//保证二号杯子比三号杯子大,避免S/2装不进上小杯子的情况。
V[1]=S,V[2]=N,V[3]=M;
bfs();
if(ans>0)printf("%d\n",ans);
else puts("impossible");
}
return
后记:
这道题是很基础的题,但是我由于各种原因,也包括懒癌的缘故,一直都没有落实下来。今天把问题好好做了一次,希望将来别在补题上那么拖延。
如有纰漏,恳请指正。( ´°̥̥̥̥̥̥̥̥ω°̥̥̥̥̥̥̥̥`)