DFS BFS 002:Pots

24 篇文章 1 订阅
11 篇文章 0 订阅

002:Pots

总时间限制: 1000ms 内存限制: 65536kB

描述

You are given two pots, having the volume of A and B liters respectively. The following operations can be performed:

FILL(i) fill the pot i (1 ≤ i ≤ 2) from the tap;
DROP(i) empty the pot i to the drain;
POUR(i,j) pour from pot i to pot j; after this operation either the pot j is full (and there may be some water left in the pot i), or the pot i is empty (and all its contents have been moved to the pot j).
Write a program to find the shortest possible sequence of these operations that will yield exactly C liters of water in one of the pots.

输入
On the first and only line are the numbers A, B, and C. These are all integers in the range from 1 to 100 and C≤max(A,B).

输出
The first line of the output must contain the length of the sequence of operations K. The following K lines must each describe one operation. If there are several sequences of minimal length, output any one of them. If the desired result can’t be achieved, the first and only line of the file must contain the word ‘impossible’.

样例输入

3 5 4

样例输出

6
FILL(2)
POUR(2,1)
DROP(1)
POUR(2,1)
FILL(2)
POUR(2,1)

解题思路
求最优解问题,可以使用广度优先搜索 或 深度优先搜索配合剪枝;

方法1

DFS深度优先方法
1.标记A,B状态是否被遍历过——visited数组
2.保存最优路径——result数组
3.剪枝——储存当前最短步骤数,方便及时停止多余的深搜
4.因为题目要判断impossible,故需要flag标志是否有解

#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;

bool visited[120][120];    //保存状态为a瓶的水量和b瓶的水量是否访问过
int temp[100000];             //保存当前路径 
int result[100000];            //保存最优路径
bool flag;                     //当无解时,返回impossible
int A,B,C;
int Minstep;            //保存最少步数 	


void Dfs(int x, int y, int step){        //a的水量,b的水量,到目前为止的步数 
	if(step>=Minstep) return;       //剪枝——步数多余目前最优解,无需继续 
	if(x==C||y==C){
		flag=1;
		if(step<Minstep){
		Minstep=step;
		for (int i=0;i<step;i++){
			result[i]=temp[i];               //将最小解的步骤放入result中 
		}	
		}	
		return; //返回,接着遍历下一种情况
	}   //接下来遍历6种情况 
					if(x<A&&!visited[A][y]){ //fill(A)   
					visited[A][y]=1;
					temp[step]=1;      //第一种case 
					Dfs(A,y,step+1);
					visited[A][y]=0;
				}
				if(y<B&&!visited[x][B]){     //fill(B)
					visited[x][B]=1;
					temp[step]=2;
					Dfs(x,B,step+1);
					visited[x][B]=0;
				}
				if(x>0&&!visited[0][y]){       //DROP(A)
					visited[0][y]=1;
					temp[step]=3;
					Dfs(0,y,step+1);
					visited[0][y]=0;
				}

			if(y>0&&!visited[x][0]){       //DROP(B)
					visited[x][0]=1;
					temp[step]=4;
					Dfs(x,0,step+1);
					visited[x][0]=0;
				}
								// //pour(a,b)
			int Num;           
				 Num=min(x,B-y);	 //a中水少则可以倒完x-x,B-y少则有剩余,剩余x-(B-y) 
				if(x>0&&y<B&&!visited[x-Num][y+Num]){
				    visited[x-Num][y+Num]=1;
				    temp[step]=5;
				    Dfs(x-Num,y+Num,step+1);
				    visited[x-Num][y+Num]=0;
			}
		 						//pour(b,a)
				 Num=min(A-x,y);	 //a中水少则可以倒完x-x,B-y少则有剩余,剩余x-(B-y) 
				if(x<A&&y>0&& !visited[x+Num][y-Num]){
				    visited[x+Num][y-Num]=1;
				    temp[step]=6;
				    Dfs(x+Num,y-Num,step+1);
				    visited[x+Num][y-Num]=0;
			}
	return; //上述都遍历过了,即可返回上层遍历后面的; 
}

int main(){
	while(cin>>A>>B>>C)  { //输入三个数
	memset(visited,0,sizeof visited);
	visited[0][0]=1;         
	Minstep=0xfffffff;
	flag=0;
	Dfs(0,0,0);          //1瓶为0,2瓶为0,步数为0; 
	if(flag){                    
	cout<<Minstep<<endl; 
	for(int i=0;i<Minstep;i++){          //将result内的步骤按要求输出          
		if(result[i]==1) cout<<"FILL(1)"<<endl;
		if(result[i]==2) cout<<"FILL(2)"<<endl;
		if(result[i]==3) cout<<"DROP(1)"<<endl;
		if(result[i]==4) cout<<"DROP(2)"<<endl;
		if(result[i]==5) cout<<"POUR(1,2)"<<endl;
		if(result[i]==6) cout<<"POUR(2,1)"<<endl;
	}}
	else{
		cout<<"impossible"<<endl;
	}
}
} 

以上代码值得注意的是:
在下四行代码中,Num必须定义在DFS函数内,

            visited[x-Num][y+Num]=1;         

            temp[step]=5;

            Dfs(x-Num,y+Num,step+1);       
            //进入下一层Dfs

            visited[x-Num][y+Num]=0;        
             //返回后将visited记录删除——而Num已经不是原来的Num了

这里第三步进入了下一层,Num就变为下一层的Num,而返回的时候Num没有变回来,所以 visited[x-Num][y+Num]=0;中的Num并不是原来的Num,而是下一层的Num,所以答案错误了。

方法2

BFS广度优先搜索思路
1.用队列实现广度优先搜索,故需要创建队列;
2.需要定义struct,保存每个点的状态:
a的水量,b的水量,前一个数据,当前操作代号;
3.同样需要数组标记遍历的该位置是否进入过队列中;
4.若使用queue库,则需要stack来储存最优路径;

#include<stdio.h>
#include<cstring>
#include<iostream>
#include<queue>
#include<stack>
using namespace std;

struct cup{
	int x,y;//两个杯子总状态
	int step; //记录操作数
	int flag; //标记操作的代号
	cup *pre; //记录该状态的前驱结点 
};

queue<cup> Q; //用于装每一次的状态,并且队列为结构体类型
stack<int> R;//用于从头到尾输出操作的代号
int a,b,e;// 输入的三个容量
int vis[110][110]={0};
int ans; //结果

void BFS(int x, int y){
	int i;
	cup c;   //杯子的当前状态 
	cup t[310];      //用于存放每一次目前瓶子里剩水的量 
	c.x=0, c.y=0;     //此时杯子中没有水 
	c.flag=0; //表示还没有操作
	c.pre=NULL; //前一个状态为空;
	c.step=0; //当前操作数为0
	Q.push(c);     //c入列
	vis[x][y]=1;    //当前x,y已被访问过 
	int count=-1;   //为当前操作的前一个操作 
	while (!Q.empty()) {
		count++;
		t[count]=Q.front();       //拿出队首 ,存在t中——供后续操作以及结果提取 
		Q.pop();          //删除队首 
		for(i=1;i<=6;i++){
			switch(i){
				case 1:     //fill(a)
						c.x=a;   // 第一个杯子装满 
						c.y=t[count].y;     //y与原来相同 
						c.flag =1;        //这次操作用了case 1  
						break;          //完事 
				case 2: //fill(b)
						c.x=t[count].x;
						c.y=b;
						c.flag=2;
						break;
				case 3: //drop(a)
						c.x=0;    //a倒掉 
						c.y=t[count].y;
						c.flag=3;
						break;
				case 4: //drop(b)
						c.x=t[count].x;    //a倒掉 
						c.y=0;
						c.flag=4;
						break;	
				case 5: //pour(a,b)
						if(t[count].x>b-t[count].y){//x中还有剩的,y满了
								c.x=t[count].x-(b-t[count].y);
								c.y=b;
						}			
						else{  //x没剩的 
							c.x=0;
							c.y=t[count].x+t[count].y;	
						}
						c.flag=5;
						break;
				case 6: //pour(b,a)
						if(t[count].y>a-t[count].x){
								c.y=t[count].y-(a-t[count].x);
								c.x=a;
						}
						else{
							c.y=0;
							c.x=t[count].x+t[count].y;
							
						}
						c.flag=6;
						break;
			}
		if(vis[c.x][c.y]) continue;
		vis[c.x][c.y]=1; //标记为访问过
		c.step=t[count].step+1;      //步数+1 
		c.pre=&t[count];       //记录步数的前驱节点
		if(c.x==e||c.y==e){
			ans=c.step;
			while(c.pre){
				R.push(c.flag);
				c=*c.pre;
			}
			return;
		} 
		Q.push(c); //不为最终点,则把该状态入列; 等待寻找该点的后面 
		}
	}
} 
void print()
{
	while(!R.empty()){
		int i=R.top();   //栈顶
		R.pop();
		switch(i)
		{
			case 1: cout<<"FILL(1)"<<endl;break;
			case 2: cout<<"FILL(2)"<<endl;break;
			case 3: cout<<"DROP(1)"<<endl;break;
			case 4: cout<<"DROP(2)"<<endl;break;
			case 5: cout<<"POUR(1,2)"<<endl;break;
			case 6: cout<<"POUR(2,1)"<<endl;break;

		 } 
	}
}
int main(){
	cin>>a>>b>>e;
	BFS(0,0);
	if(ans==0) cout<<"impossible"<<endl;
	else{
		cout<<ans<<endl;
		print();
	}
	return 0;
}

总结:
最短路径问题:
深搜需要剪枝以提高计算速度,减少无效遍历,增加一个数组存储路径即可;
广搜可以快速得到最短距离,但是要记录路径略显麻烦。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值