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



题目:两瓶 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种转移。


即:
<pre name="code" class="cpp">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<iostream>
#include<string.h>
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]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值