旧题新解-用动态规划解决四人分酒问题



题目:两瓶 8 两的酒,一个 3 两的杯子,瓶和杯子都没有刻度,如何将酒平均分给四个人喝?也就是每个人分 4 两酒,也没什么限制条件。

当然,喝过的酒不能再吐出来(: > )。

回溯法解法(我看到的)

http://blog.csdn.net/clearriver/article/details/4418611?utm_source=jiancool
在大学c语言课上要求讲回溯法解题报告的时候搜到的这题,当时我看的是一篇09年的解题报告,用的是回溯法解决。不过实际上,如果只输出最终的最优解的话,回溯法效率有些低(我看的那个版本是运行了8秒。)。其实如果用动态规划的方式的话,可以很快解决这个问题(只输出结果的话用我的电脑跑大概是0.03秒,输出路径的话是0.24秒)。


那么,该如何用动态规划解决这个问题呢?我们把2个瓶子,1个杯子和4个人喝下的酒的量作为参数,建立一个状态转移方程。这个方程有5种转移方式,即瓶子倒给瓶子,瓶子倒给人,瓶子倒给杯子,杯子倒给瓶子,杯子倒给人。
如果用一个长度为7的1维整数数组a存储当前状态,那么a[0],a[1],a[2]向a数组所有元素倒酒(不能倒向自己)的18种情况就可以枚举全部18种转移。


即:
for (int i=0;i<3;i++)
		for(int j=0;j<7;j++)
		{ 
		int *px=new int[7]; 
		for (int k=0;k<7;k++)
			px[k]=pp[k];
		
		
		n=play(px,i,j); 
		
		
		
		if (n!=0)
		{
			minso=min(minso,solve(px,nowstep+1));
			if (minso!=frontm)
			{
				for (int k=0;k<7;k++)
					answer[pp[0]][pp[1]][pp[2]][pp[3]][pp[4]][pp[5]][pp[6]][k]=px[k];
				answer[pp[0]][pp[1]][pp[2]][pp[3]][pp[4]][pp[5]][pp[6]][7]=i+1;
				answer[pp[0]][pp[1]][pp[2]][pp[3]][pp[4]][pp[5]][pp[6]][8]=j+1; 
			}
			frontm=minso;
		}		
		}



其中,play代表i往j里倒酒的最大量,0表示无法这么倒酒,那么就不会处理这种情况,不然就更新现有最小值,然后如果最小值被更新了(minso!=frontm),就记录下这个点的最小值是怎么被更新的(用answer记录)。

还有一个问题就是,这个问题和一般的dp不一样,并没有很明确的转移方向,所以类似宽搜(当年我高中的时候似乎用宽搜写过类似的题,所以这题应该也可以用宽搜,而且效率应该会更高。不过用dp的话可以顺手练习一下dp,因为似乎dp除了竞赛还是很少用的。)的方式记录正在被处理的点。

其实写算法的时候还有一个很神奇的问题,就是这句话

if ((solution[pp[0]][pp[1]][pp[2]][pp[3]][pp[4]][pp[5]][pp[6]]!=0)&&(timestep[pp[0]][pp[1]][pp[2]][pp[3]][pp[4]][pp[5]][pp[6]]<=nowstep))
	{
		return solution[pp[0]][pp[1]][pp[2]][pp[3]][pp[4]][pp[5]][pp[6]];
	}
因为可能出现一个本来在后面的状态先被找到,然后再找到原本在前面的那个状态,然后本来在前面的那个状态在找寻的时候可能由于原本后面的那个状态现在正在被寻找不能找回去所以没有找到正确解。然后当本来该在前面的那个状态被找到的时候,由于已经被找过了,所以原本最优的解会被漏过(原本的最优解变成了无解,因为被略过的那个状态并没有被重新找过。)。所以记录到达这种状态所需的步数,如果更快的到达了某个状态,就重新计算以刷新它,这样就不会漏过解了,但是时间会变得复杂一些。

额,比较特别的地方应该就是这几个,然后上代码。熬夜赶出来的所以不是很精简,也懒得优化了。

#include
#include
using namespace std;
int solution[9][9][4][5][5][5][5];//用来记录某个状态到终点的最低步数 
int timestep[9][9][4][5][5][5][5];//用来记录到达某个状态的最低步数 
bool doing[9][9][4][5][5][5][5];//用来记录某个状态是否正在被执行 
int answer[9][9][4][5][5][5][5][9];//用来记录到达某个状态之后的状态,和到达这个状态的操作

int min(int a,int b)//求最小值,经常用到 
{
	if(a>b)
		return b;
	return a;
}
int pp(int a,int b)//瓶子到瓶子 
{
	
	return min(a,8-b);
}
int pc(int a,int b)//瓶子到杯子 
{
		return min(a,3-b);
}
int cp(int a,int b)//杯子到瓶子 
{

		return min(a,8-b);
}
int pr(int a,int b)//瓶子到人 
{
	if (a<=(4-b))
	{
		return a;
	}
	return 0;
}
int cr(int a,int b)//杯子到人 
{
	if (a<=(4-b))
	{
		return a;
	}
	return 0;
}
int play(int num[],int b1,int b2)//对于某个状态的,试着用b1号容器往b2号容器里倒 
{	
	if(b1<2)
	{	
		if (b2<2)
		{
			int j=pp(num[b1],num[b2]);
			if (j!=0)
			{
				num[b1]-=j;
				num[b2]+=j;
				return j;
			}
			else
			{
				return 0;
			}
		}
		if (b2==2)
		{
			int j=pc(num[b1],num[b2]);
			if (j!=0)
			{
				num[b1]-=j;
				num[b2]+=j;
				return j;
			}
		}
		if (b2>2)
		{
	
			int j=pr(num[b1],num[b2]);
			if (j!=0)
			{
				num[b1]-=j;
				num[b2]+=j;
				return j;
			}
		}
	}
	if (b1==2)
	{
		if (b2<2)
		{			
			int j=cp(num[b1],num[b2]);			
			if (j!=0)
			{
				num[b1]-=j;
				num[b2]+=j;
				return j;
			}
		}		
		if (b2>2)
		{
			int j=cr(num[b1],num[b2]);
			if (j!=0)
			{
				num[b1]-=j;
				num[b2]+=j;
				return j;
			}
		}
	}
	return 0;
}
int solve(int pp[],int nowstep)//大家都懂的dp 
{
	if ((solution[pp[0]][pp[1]][pp[2]][pp[3]][pp[4]][pp[5]][pp[6]]!=0)&&(timestep[pp[0]][pp[1]][pp[2]][pp[3]][pp[4]][pp[5]][pp[6]]<=nowstep))
	{
		return solution[pp[0]][pp[1]][pp[2]][pp[3]][pp[4]][pp[5]][pp[6]];
	}
	if ((pp[3]==4)&&(pp[4]==4)&&(pp[5]==4)&&(pp[6]==4))
	{
		return 0;
	}
		if (doing[pp[0]][pp[1]][pp[2]][pp[3]][pp[4]][pp[5]][pp[6]])
	{
		return 100000;
	}
	doing[pp[0]][pp[1]][pp[2]][pp[3]][pp[4]][pp[5]][pp[6]]=true;
	int minso=100000;
	int frontm=100000;
	int n=0;
	for (int i=0;i<3;i++)
		for(int j=0;j<7;j++)
		{ 
		int *px=new int[7]; 
		for (int k=0;k<7;k++)
			px[k]=pp[k];
		
		
		n=play(px,i,j); 
		
		
		
		if (n!=0)
		{
			minso=min(minso,solve(px,nowstep+1));
			if (minso!=frontm)
			{
				for (int k=0;k<7;k++)
					answer[pp[0]][pp[1]][pp[2]][pp[3]][pp[4]][pp[5]][pp[6]][k]=px[k];
				answer[pp[0]][pp[1]][pp[2]][pp[3]][pp[4]][pp[5]][pp[6]][7]=i+1;
				answer[pp[0]][pp[1]][pp[2]][pp[3]][pp[4]][pp[5]][pp[6]][8]=j+1; 
			}
			frontm=minso;
		}		
		} 		
	doing[pp[0]][pp[1]][pp[2]][pp[3]][pp[4]][pp[5]][pp[6]]=false;
	timestep[pp[0]][pp[1]][pp[2]][pp[3]][pp[4]][pp[5]][pp[6]]=nowstep;
	solution[pp[0]][pp[1]][pp[2]][pp[3]][pp[4]][pp[5]][pp[6]]=minso+1;
	return minso+1;
}
int main()
{
	memset(solution,0,sizeof(solution));
	memset(doing,0,sizeof(doing));
	memset(timestep,0,sizeof(timestep));
	memset(answer,0,sizeof(answer));
	
//	cout<<(sizeof(solution)+sizeof(doing)+sizeof(answer))/1024<"<< answer[p1][p2][b][r1][r2][r3][r4][8]<
旧题新解-用动态规划解决四人分酒问题_第1张图片
这里其实有一个我也不是很清楚的情况,用下面这种代码的话,虽然证实会漏掉一些情况,但是也可以得到24步的答案,是否说明这个算法还有优化的空间呢?


#include
#include
using namespace std;
int solution[9][9][4][5][5][5][5];//用来记录某个状态到终点的最低步数 
//int timestep[9][9][4][5][5][5][5];//用来记录到达某个状态的最低步数 
bool doing[9][9][4][5][5][5][5];//用来记录某个状态是否正在被执行 
int answer[9][9][4][5][5][5][5][9];//用来记录到达某个状态之后的状态,和到达这个状态的操作


int min(int a,int b)//求最小值,经常用到 
{
	if(a>b)
		return b;
	return a;
}
int pp(int a,int b)//瓶子到瓶子 
{
	return min(a,8-b);
}
int pc(int a,int b)//瓶子到杯子 
{
		return min(a,3-b);
}
int cp(int a,int b)//杯子到瓶子 
{


		return min(a,8-b);
}
int pr(int a,int b)//瓶子到人 
{
	if (a<=(4-b))
	{
		return a;
	}
	return 0;
}
int cr(int a,int b)//杯子到人 
{
	if (a<=(4-b))
	{
		return a;
	}
	return 0;
}
int play(int num[],int b1,int b2)//对于某个状态的,试着用b1号容器往b2号容器里倒 
{	
	if(b1<2)
	{	
		if (b2<2)
		{
			int j=pp(num[b1],num[b2]);
			if (j!=0)
			{
				num[b1]-=j;
				num[b2]+=j;
				return j;
			}
		}
		if (b2==2)
		{
			int j=pc(num[b1],num[b2]);
			if (j!=0)
			{
				num[b1]-=j;
				num[b2]+=j;
				return j;
			}
		}
		if (b2>2)
		{
	
			int j=pr(num[b1],num[b2]);
			if (j!=0)
			{
				num[b1]-=j;
				num[b2]+=j;
				return j;
			}
		}
	}
	if (b1==2)
	{
		if (b2<2)
		{			
			int j=cp(num[b1],num[b2]);			
			if (j!=0)
			{
				num[b1]-=j;
				num[b2]+=j;
				return j;
			}
		}		
		if (b2>2)
		{
			int j=cr(num[b1],num[b2]);
			if (j!=0)
			{
				num[b1]-=j;
				num[b2]+=j;
				return j;
			}
		}
	}
	return 0;
}
int solve(int pp[],int nowstep)//大家都懂的dp 
{
	if (solution[pp[0]][pp[1]][pp[2]][pp[3]][pp[4]][pp[5]][pp[6]]!=0)
	{
		return solution[pp[0]][pp[1]][pp[2]][pp[3]][pp[4]][pp[5]][pp[6]];
	}
	if ((pp[3]==4)&&(pp[4]==4)&&(pp[5]==4)&&(pp[6]==4))
	{
		return 0;
	}
		if (doing[pp[0]][pp[1]][pp[2]][pp[3]][pp[4]][pp[5]][pp[6]])
	{
		return 100000;
	}
	doing[pp[0]][pp[1]][pp[2]][pp[3]][pp[4]][pp[5]][pp[6]]=true;
	int minso=100000;
	int frontm=100000;
	int n=0;
	for (int i=0;i<3;i++)
		for(int j=0;j<7;j++)
				int *px=new int[7]; 
		for (int k=0;k<7;k++)
			px[k]=pp[k];	
		n=play(px,i,j); 
		if((i<2)&&(j<2)&&((px[i]==8)||(px[j]==8)))
		{
			n=0;
			//cout<<"back"<		}	
		
		if (n!=0)
		{
			minso=min(minso,solve(px,nowstep+1));
			if (minso!=frontm)
			{
				for (int k=0;k<7;k++)
					answer[pp[0]][pp[1]][pp[2]][pp[3]][pp[4]][pp[5]][pp[6]][k]=px[k];
				answer[pp[0]][pp[1]][pp[2]][pp[3]][pp[4]][pp[5]][pp[6]][7]=i+1;
				answer[pp[0]][pp[1]][pp[2]][pp[3]][pp[4]][pp[5]][pp[6]][8]=j+1; 
			}
			frontm=minso;
		}		
		} 		
	doing[pp[0]][pp[1]][pp[2]][pp[3]][pp[4]][pp[5]][pp[6]]=false;
//	timestep[pp[0]][pp[1]][pp[2]][pp[3]][pp[4]][pp[5]][pp[6]]=nowstep;
	solution[pp[0]][pp[1]][pp[2]][pp[3]][pp[4]][pp[5]][pp[6]]=minso+1;
	return minso+1;
}
int main()
{
	memset(solution,0,sizeof(solution));
	memset(doing,0,sizeof(doing));
//	memset(timestep,0,sizeof(timestep));
	memset(answer,0,sizeof(answer));
	
//	cout<<(sizeof(solution)+sizeof(doing)+sizeof(answer))/1024<	int *sr=new int[7];
	sr[0]=8;
	sr[1]=8;
	sr[2]=0;
	sr[3]=0;
	sr[4]=0;
	sr[5]=0;
	sr[6]=0;//因为用数组存数据,所以准备有点麻烦 
	cout<	
	//开始输出路径 
	int p1=8;
	int p2=8;
	int b=0;
	int r1=0;
	int r2=0;
	int r3=0;
	int r4=0;
	cout<	
	while (!((r1==4)&&(r2==4)&&(r3==4)&&(r4==4)))
	{
		cout<"<< answer[p1][p2][b][r1][r2][r3][r4][8]<		int tp1=answer[p1][p2][b][r1][r2][r3][r4][0];
		int tp2=answer[p1][p2][b][r1][r2][r3][r4][1];
		int tb=answer[p1][p2][b][r1][r2][r3][r4][2];
		int tr1=answer[p1][p2][b][r1][r2][r3][r4][3];
		int tr2=answer[p1][p2][b][r1][r2][r3][r4][4];
		int tr3=answer[p1][p2][b][r1][r2][r3][r4][5];
		int tr4=answer[p1][p2][b][r1][r2][r3][r4][6];
		p1=tp1;
		p2=tp2;
		b=tb;
		r1=tr1;
		r2=tr2;
		r3=tr3;
		r4=tr4;
		cout<					
		return 0;
}

旧题新解-用动态规划解决四人分酒问题_第2张图片

快了很多,但是为什么呢?现在大概只能说是一个巧合吧。

因为没有什么注释的好习惯,又是第一次写博客,可能写的不是很清楚,希望能帮助到各位对dp不太熟悉的人。

什么,你精通dp?你是算法大牛?能看到这里真是有耐心。欢迎加qq3220665887,一起讨论算法或者编程问题。



你可能感兴趣的:(c++,动态规划)