今天提交的时候有点小郁闷,第一次提交把编译器选错了(选了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;
}