浙大ZOJ 1005 Jugs问题解决

今天提交的时候有点小郁闷,第一次提交把编译器选错了(选了GCC),太大意了!

一、工程代码及算法设计注释

--------------------------------------------------jugs.h----------------------------------------------

#ifndef JLU_CCST_GDC_JUGS_H
#define JLU_CCST_GDC_JUGS_H

extern bool findJugsSolution(int nA,int nB,int cA,int cB,int N);
extern void testFindJugsSolution();

#endif//JLU_CCST_GDC_JUGS_H

-------------------------------------------------- jugs.cpp ----------------------------------------------

/**
题目来源:浙大ACM在线测试——ZOJ,题目编号1005,题名"Jugs"
URL:http://acm.zju.edu.cn/onlinejudge/showProblem.do?problemCode=1005
Author:hellogdc<gdcjlu@163.com>
Finish Time:2014.01.03
*/

/**
算法设计:
利用递归思想:
求解某一个状态下的可解序列可以通过以下几个动作,将问题转化为求解子问题的可解序列。
某一个状态(nA,nB,cA,cB,N),其中nA、nB分别表示在A罐和B罐中水的量,cA、cB分别表示A罐和B罐中的容量,N为B罐需要达到的目标,
可以做的动作如下:
1. Fill A。可以采取该动作的条件是:nA<cA。
2. Empty A。可以采取该动作的条件是:nA>0.
3. Pour B A。可以采取该动作的条件是:nB>0&&nA<cA。
4. Fill B。可以采取该动作的条件是:nB<cB。
5. Empty B。可以采取该动作的条件是:nB>0。
6. Pour A B。可以采取该动作的条件是:nA>0&&nB<cB。

递归出口:nB==N || cB<N

问题:这个递归会一直执行,直到找到一个可解序列。因为是穷举法,所以如果存在解,那么终会结束。问题是,
如果无解,那么该递归将会一直执行下去。怎么办呢?需要设置一个递归深度什么的吗?

*/

#include <iostream>
#include <iomanip>
#include <string>
#include <list>
#include <set>
using namespace std;

namespace acm_gdc{
	const int ACTIONS_COUNT=6;
	string actions[ACTIONS_COUNT]={"Fill A","Empty A","Fill B","Empty B","Pour A B","Pour B A"};
	list<string> solution;

	struct Status{//findJugsSolution2(),findJugsSolution3(),findJugsSolution4()
		int x,y;
		Status(int xx,int yy):x(xx),y(yy) {};
		bool operator==(const Status& s1){
			return (x==s1.x&&y==s1.y);
		}
		string toString(){
			char str[20];
			sprintf(str,"(%d,%d)",x,y);
			return string(str);
		}
	};

	int prevOp=4;//记录上一步的操作

	list<Status> listForStack;//findJugsSolution3(),findJugsSolution4()

	/**
	提高性能:
	1. 上述算法虽可以执行,但仔细一分析,就会发现它做了很多无用功。比如Fill和Empth反复间隔执行。为了
	去掉无用动作,提高性能,对上述回溯做如下剪枝处理:
	1.1 Fill A和EmpthA不能连续执行。
	1.2 Fill B和EmpthB不能连续执行。
	1.3 Pour B A和Pour A B不能连续执行。
	为此,需要一个辅助变量,记录上一步的操作。

	2. 一执行,发现还是执行太长时间。仔细一分析,发现还是存在一些无用动作:
	2.1 FillA后能执行的动作只有Pour A B。
	2.2 FillB后能执行的动作只有Pour B A。
	2.3 EmptyA后能执行的动作只有Pour B A。
	2.4 EmptyB后能执行的动作只有Pour A B。
	*/
	const int DEPTH_MAX=50;
	bool findJugsSolution1(int nA,int nB,int cA,int cB,int N,int depth,int maxDepth){
		//出口
		if(depth>maxDepth||cB<N)
			return false;
		if(nB==N){
			for(list<string>::iterator it=solution.begin();it!=solution.end();it++){
				cout<<(*it)<<endl;
			}
			cout<<"success"<<endl;
			return true;
		}
		//执行子动作
		for(int i=0;i<ACTIONS_COUNT;i++){
			//可以执行0,1,2,3步
			if(prevOp==4||prevOp==5){
				switch(i){
				case 0://Fill A
					//if(nA>=cA||prevOp==1)
					if(nA>=cA)
						continue;
					nA=cA;
					break;
				case 1://Empty A
					//if(nA<=0||prevOp==0)
					if(nA<=0)
						continue;
					nA=0;
					break;

				case 2://Fill B
					//if(nB>=cB||prevOp==4)
					if(nB>=cB)
						continue;
					nB=cB;
					break;
				case 3://Empty B
					//if(nB<=0||prevOp==3)
					if(nB<=0)
						continue;
					nB=0;
					break;
				default:
					continue;
				}
			}else{
				switch(i){
				case 5://Pour B A
					if(nB<=0||nA>=cA)
						continue;
					if(nB>(cA-nA)){
						nB-=(cA-nA);
						nA=cA;					
					}else{
						nA+=nB;
						nB=0;
					}
					break;
				case 4://Pour A B
					if(nA<=0||nB>=cB)
						continue;
					if(nA>(cB-nB)){
						nA-=(cB-nB);
						nB=cB;					
					}else{
						nB+=nA;
						nA=0;
					}
					break;
				default:
					continue;
				}
			}
			prevOp=i;
			//将动作压栈
			solution.push_back(actions[i]);
			if( findJugsSolution1(nA,nB,cA,cB,N,depth+1,maxDepth) )
				return true;
			//恢复栈
			solution.pop_back();
		}
		return false;
	}

	/**
	不行,性能还是不行,还发现一个剪枝的办法,那就是,一个状态只能出现一次,如果重复出现,那么将下面的分支剪掉。
	这样就需要一个保存状态的列表,元素最多为cA*cB。
	但是结果却是错误的,蛋疼!估计是由于我采用了findJugsSolution()中的那些繁杂的剪枝方法。
	这样,如果能结合最初始的回溯方法,再结合这里的状态检查,这样便能保证在cA*cB次递归内结束递归,找到相应的解。
	*/
	struct StatusComparator{
		bool operator()(const Status& s1,const Status& s2){
			if(s1.x<s2.x)
				return true;
			if( s1.x==s2.x && s1.y<s2.y )
				return true;
			return false;
		}
	};
	set<Status,StatusComparator> statusSet;
	bool findJugsSolution2(int nA,int nB,int cA,int cB,int N){
		//出口
		if(cB<N)
			return false;
		if(nB==N){
			for(list<string>::iterator it=solution.begin();it!=solution.end();it++){
				cout<<(*it)<<endl;
			}
			cout<<"success"<<endl;
			return true;
		}
		//检查该状态是否已经出现
		Status st(nA,nB);
		if(statusSet.find(st)!=statusSet.end())
			return false;
		statusSet.insert(st);
		//执行子动作
		for(int i=0;i<ACTIONS_COUNT;i++){
			switch(i){
			case 0://Fill A
				if(nA>=cA)
					continue;
				nA=cA;
				break;
			case 1://Empty A
				if(nA<=0)
					continue;
				nA=0;
				break;
			case 2://Fill B
				if(nB>=cB)
					continue;
				nB=cB;
				break;
			case 3://Empty B
				if(nB<=0)
					continue;
				nB=0;
				break;		
			case 4://Pour A B
				if(nA<=0||nB>=cB)
					continue;
				if(nA>(cB-nB)){
					nA-=(cB-nB);
					nB=cB;				
				}else{
					nB+=nA;
					nA=0;
				}
				break;
			case 5://Pour B A
				if(nB<=0||nA>=cA)
					continue;
				if(nB>(cA-nA)){
					nB-=(cA-nA);
					nA=cA;				
				}else{
					nA+=nB;
					nB=0;
				}
				break;
			default:
				continue;
			}
			//将动作压栈
			solution.push_back(actions[i]);
			if( findJugsSolution2(nA,nB,cA,cB,N) )
				return true;
			//恢复栈
			solution.pop_back();
		}
		return false;
	}

	/**
	经过执行,发现findJugsSolution2()中关于状态检测的分析是错误的,因为不同分支里状态有可能会出现重复。
	那可不可以这样:每个分支路有一个状态表来监测该分支路是否可以继续下去。
	这样就出现一个问题:如何给每个分支路维护这么一个状态表?解决方法就是栈。
	*/
	bool findJugsSolution3(int nA,int nB,int cA,int cB,int N){
		//出口
		if(cB<N)
			return false;
		if(nB==N){
			for(list<string>::iterator it=solution.begin();it!=solution.end();it++){
				cout<<(*it)<<endl;
			}
			cout<<"success"<<endl;
			return true;
		}
		//检查该状态是否已经出现
		Status st(nA,nB);
		for(list<Status>::iterator it=listForStack.begin();it!=listForStack.end();it++){
			if((*it)==st)
				return false;
		}
		listForStack.push_front(st);

		//执行子动作
		bool isFound=false;
		int nABack=nA,nBBack=nB;
		for(int i=0;i<ACTIONS_COUNT&&(!isFound);i++){
			switch(i){
			case 0://Fill A
				if(nA>=cA)
					continue;
				nA=cA;
				break;
			case 1://Empty A
				if(nA<=0)
					continue;
				nA=0;
				break;
			case 2://Fill B
				if(nB>=cB)
					continue;
				nB=cB;
				break;
			case 3://Empty B
				if(nB<=0)
					continue;
				nB=0;
				break;		
			case 4://Pour A B
				if(nA<=0||nB>=cB)
					continue;
				if(nA>(cB-nB)){
					nA-=(cB-nB);
					nB=cB;
				}else{
					nB+=nA;
					nA=0;
				}
				break;
			case 5://Pour B A
				if(nB<=0||nA>=cA)
					continue;
				if(nB>(cA-nA)){
					nB-=(cA-nA);
					nA=cA;
				}else{
					nA+=nB;
					nB=0;
				}
				break;
			default:
				continue;
			}		
			//将动作压栈
			solution.push_back(actions[i]);
			if( findJugsSolution3(nA,nB,cA,cB,N) ){
				isFound=true;
			}
			//恢复栈
			solution.pop_back();
			//回复nA和nB
			nA=nABack;nB=nBBack;
		}
		//恢复栈
		listForStack.pop_front();

		return isFound;
	}

	/**
	findJugsSolution3()虽然已经可以得到正确的结果,但是仍然会出现一些冗余步骤。
	所以再加上如下规定:即参考findJugsSolution()中的分析
	2.1 FillA后能执行的动作只有Pour A B。
	2.2 FillB后能执行的动作只有Pour B A。
	2.3 EmptyA后能执行的动作只有Pour B A。
	2.4 EmptyB后能执行的动作只有Pour A B。
	*/
	bool findJugsSolution4(int nA,int nB,int cA,int cB,int N){
		//出口
		if(cB<N)
			return false;
		//检查该状态是否已经出现
		Status st(nA,nB);
		for(list<Status>::iterator it=listForStack.begin();it!=listForStack.end();it++){
			if((*it)==st)
				return false;
		}
		listForStack.push_back(st);

		if(nB==N){
			list<Status>::iterator sIt=listForStack.begin();
			sIt++;//状态(0,0)就不输出了
			for(list<string>::iterator it=solution.begin();it!=solution.end();it++){
				cout<<setw(actions[ACTIONS_COUNT-1].length())<<(*it)<<'\t'<<(sIt->toString())<<endl;
				sIt++;
			}
			cout<<"success"<<endl;
			return true;
		}

		//执行子动作
		bool isFound=false;
		int nABack=nA,nBBack=nB,prevOpBack=prevOp;
		for(int i=0;i<ACTIONS_COUNT&&(!isFound);i++){
			//可以执行0,1,2,3步
			if(prevOp==4||prevOp==5){
				switch(i){
				case 0://Fill A
					if(nA>=cA)
						continue;
					nA=cA;
					break;
				case 1://Empty A
					if(nA<=0)
						continue;
					nA=0;
					break;

				case 2://Fill B
					if(nB>=cB)
						continue;
					nB=cB;
					break;
				case 3://Empty B
					if(nB<=0)
						continue;
					nB=0;
					break;
				default:
					continue;
				}
			}else{
				switch(i){
				case 5://Pour B A
					if(nB<=0||nA>=cA)
						continue;
					if(nB>(cA-nA)){
						nB-=(cA-nA);
						nA=cA;					
					}else{
						nA+=nB;
						nB=0;
					}
					break;
				case 4://Pour A B
					if(nA<=0||nB>=cB)
						continue;
					if(nA>(cB-nB)){
						nA-=(cB-nB);
						nB=cB;					
					}else{
						nB+=nA;
						nA=0;
					}
					break;
				default:
					continue;
				}
			}
			prevOp=i;
			//将动作压栈
			solution.push_back(actions[i]);
			if( findJugsSolution4(nA,nB,cA,cB,N) ){
				isFound=true;
			}
			//恢复栈
			solution.pop_back();
			//恢复nA、nB和prevOp
			nA=nABack;nB=nBBack;prevOp=prevOpBack;
		}
		//恢复栈
		listForStack.pop_back();

		return isFound;
	}

	void testFindJugsSolution1(){
		int nA=0,nB=0,cA=3,cB=5,N=4;
		bool isFound=findJugsSolution1(nA,nB,cA,cB,N,0,DEPTH_MAX);
		if(!isFound)
			cout<<"No solution"<<endl;
		cout<<"End"<<endl;
	}

	void testFindJugsSolution2(){
		int nA=0,nB=0,cA=3,cB=5,N=4;
		bool isFound=findJugsSolution2(nA,nB,cA,cB,N);
		if(!isFound)
			cout<<"No solution"<<endl;
		cout<<"**************************************************************"<<endl;
		nA=nB=0;cA=5;cB=7;N=3;
		isFound=findJugsSolution2(nA,nB,cA,cB,N);
		if(!isFound)
			cout<<"No solution"<<endl;
		cout<<"**************************************************************"<<endl;
	}

	void testFindJugsSolution3(){
		int nA=0,nB=0,cA=3,cB=5,N=4;
		bool isFound=findJugsSolution3(nA,nB,cA,cB,N);
		if(!isFound)
			cout<<"No solution"<<endl;
		cout<<"**************************************************************"<<endl;
		nA=nB=0;cA=5;cB=7;N=3;
		isFound=findJugsSolution3(nA,nB,cA,cB,N);
		if(!isFound)
			cout<<"No solution"<<endl;
		cout<<"**************************************************************"<<endl;
	}

	void testFindJugsSolution4(){
		int nA=0,nB=0,cA=3,cB=5,N=4;
		bool isFound=true;
		prevOp=4;//这么做,是为了让第1步为actions中的前4个动作
		solution.clear();
		listForStack.clear();
		isFound=findJugsSolution4(nA,nB,cA,cB,N);
		if(!isFound)
			cout<<"No solution"<<endl;
		cout<<"**************************************************************"<<endl;
		nA=nB=0;cA=5;cB=7;N=3;
		prevOp=4;//这么做,是为了让第1步为actions中的前4个动作
		solution.clear();
		listForStack.clear();
		isFound=findJugsSolution4(nA,nB,cA,cB,N);
		if(!isFound)
			cout<<"No solution"<<endl;
		cout<<"**************************************************************"<<endl;
	}

	void testAll(){
		/**
		经测试:
		findJugsSolution1()既慢又得不到正确结果
		findJugsSolution2够快,但是得不到正确结果
		findJugsSolution3()较慢,但可以得到正确结果,虽然结果中包含很多冗余的步骤
		findJugsSolution4则既可以较快的得到正确结果,也将部分冗余步骤删掉了
		*/
		testFindJugsSolution1();
		testFindJugsSolution2();
		testFindJugsSolution3();
		testFindJugsSolution4();
	}
}

bool findJugsSolution(int nA,int nB,int cA,int cB,int N){
	return acm_gdc::findJugsSolution4(nA,nB,cA,cB,N);
}

void testFindJugsSolution(){
	acm_gdc::testFindJugsSolution4();
}

-------------------------------------------------- main.cpp ----------------------------------------------

#if 1

#include "jugs.h"

int main(){
	testFindJugsSolution();
}

#endif

二、 提交并被ZOJ成功接受的代码——算法核心代码

--------------------------------------------------submit_main.cpp----------------------------------------------

/**
all code are copied from jugs.cpp
*/
#include <iostream>
#include <iomanip>
#include <string>
#include <list>
#include <set>
using namespace std;
const int ACTIONS_COUNT=6;
string actions[ACTIONS_COUNT]={"fill A","empty A","fill B","empty B","pour A B","pour B A"};
list<string> solution;
struct Status{
	int x,y;
	Status(int xx,int yy):x(xx),y(yy) {};
	bool operator==(const Status& s1){
		return (x==s1.x&&y==s1.y);
	}
};
int prevOp=4;
list<Status> listForStack;
bool findJugsSolutionSubmit(int nA,int nB,int cA,int cB,int N){
	if(cB<N)
		return false;
	Status st(nA,nB);
	for(list<Status>::iterator it=listForStack.begin();it!=listForStack.end();it++){
		if((*it)==st)
			return false;
	}
	listForStack.push_back(st);
	if(nB==N){
		for(list<string>::iterator it=solution.begin();it!=solution.end();it++){
			cout<<(*it)<<endl;
		}
		cout<<"success"<<endl;
		return true;
	}
	bool isFound=false;
	int nABack=nA,nBBack=nB,prevOpBack=prevOp;
	for(int i=0;i<ACTIONS_COUNT&&(!isFound);i++){
		if(prevOp==4||prevOp==5){
			switch(i){
			case 0://Fill A
				if(nA>=cA)
					continue;
				nA=cA;
				break;
			case 1://Empty A
				if(nA<=0)
					continue;
				nA=0;
				break;

			case 2://Fill B
				if(nB>=cB)
					continue;
				nB=cB;
				break;
			case 3://Empty B
				if(nB<=0)
					continue;
				nB=0;
				break;
			default:
				continue;
			}
		}else{
			switch(i){
			case 5://Pour B A
				if(nB<=0||nA>=cA)
					continue;
				if(nB>(cA-nA)){
					nB-=(cA-nA);
					nA=cA;					
				}else{
					nA+=nB;
					nB=0;
				}
				break;
			case 4://Pour A B
				if(nA<=0||nB>=cB)
					continue;
				if(nA>(cB-nB)){
					nA-=(cB-nB);
					nB=cB;					
				}else{
					nB+=nA;
					nA=0;
				}
				break;
			default:
				continue;
			}
		}
		prevOp=i;
		solution.push_back(actions[i]);
		if( findJugsSolutionSubmit(nA,nB,cA,cB,N) ){
			isFound=true;
		}
		solution.pop_back();
		nA=nABack;nB=nBBack;prevOp=prevOpBack;
	}
	listForStack.pop_back();

	return isFound;
}

int main(){
	int A,B,target;
	while(cin>>A>>B>>target){
		prevOp=4;
		solution.clear();
		listForStack.clear();
		findJugsSolutionSubmit(0,0,A,B,target);
	}
	return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值