动态规划-砝码称重问题

    动态规划(Dynamic Programming)这个词乍一听感觉甚是高大上,初次学习或者使用的时候会感觉难以理解,这是正常的,毕竟凡事都是一回生二回熟。其实它也不难的,大家要明白一个道理,能写到课本上给学生学习的东西必然属于不难的东西,因为太难的东西写到课本上读者接受不了,这本书就没有出版的意义了。当然我说的不难也仅仅只是说动态规划的思想不难,因为我们常常面临着一个棘手的问题—那就是这个理论应用到实践中的时候,它没有一个公式或者现成的可以直接用来套用的模型。对于这一点,没有办法,只能是通过多种案例,自己多总结,多思考,看看每一种能用动态规划理论解决的问题,状态模型究竟是如何建立的,而这就需要读者多多练习了。

    本文通过华为OJ上一个基本题-砝码称重问题来让初学者消化动态规划。

    先来读一读题目,题目如下:

    现有一组砝码,重量互不相等,分别为m1、m2……mn;他们可取的最大数量分别为x1x2……xn。现在要用这些砝码去称物体的重量,问能称出多少种不同的重量。

    看完这个题目感觉似乎无从下手,难道要一个个的枚举拼凑吗?答案是否定的。不绕弯子了,下面直接进入动态规划的主场。我们假定砝码的重量单位是克,下文中不添加这个单位,只说数字,比如重量是1,表示重量是1克。

    设想第0种情况,假如现在只有0个砝码,问它能称出的重量有几种?是个人都知道只能称0的重量,并且只有这么1种情况。

    设想第1种情况,现在只有1种规格的砝码,它的重量是1,数量是1个,问它能称出的重量有几种?很明显,能称出0和1这2种重量,一共2种情况。

    设想第2种情况,现在有2种规格的砝码,它们的重量分别是1和2,1克的砝码1个,2克的砝码1个,问它能称出的重量有几种?用手比划比划应该能知道,能称出0,1,2,3这4种重量,4种情况。

    。。。

    。。。

    设想第n种情况,现在有n种规格的砝码,他们的重量分别为m1,m2。。。mn,问它能称出的重量是几种?这个时候貌似不是用手比划比划就可以出来结果了。但是这个问题的提出,就相当于是题目最终要解决的问题了。如果能够利用某种规律回答这第n种情况的提问,那么砝码称重的问题就可以完全解决。问题来了,上面的分析过程是否存在一个规律呢?答案是规律肯定存在,并且分析这种规律的思路就是动态规划的思路。

    现在我们把第n种情况能称出的重量种数用f(n)来表示,可以试着这样具体化的描述,第0种情况能称出的重量有f(0)种,第1种情况能称出的重量有f(1)种,第2种情况能称出的重量有f(2)种,。。。第n种情况能称出的重量有f(n)种。

    下面,回归到上面的提到的情况分析中去。

    第0种情况,f(0)=1;

    第1种情况,f(1)=2;如果把这个结果和第0种情况结合起来看,可以认为:f(1)=f(0)+1,此式意义非凡,它的意义为:第1种情况可以建立在第0种情况的基础上去解决,单单来看情况1的砝码可以称1种重量(即用一个1克的砝码称1克的物体)不同于情况0中,二者相加即为f(1)的结果。

    第2种情况,f(2)=4=f(1)+2,为什么加2?因为如果仅仅只有1个1克的砝码和1个2克的砝码,它只能称出重量为2克,3克这两种不同于情况1中的重量(情况1中已经解决了0克和1克的问题)。

    现在假定已经解决了第n-1种情况,即f(n-1)的值已经知道,那么我们如果能计算出单单第n种情况能称出哪些不同于n-1种情况中的重量,假定为x种,那么f(n)=f(n-1)+x。最终问题便得到了解决。

    请仔细领会上面的这个分析过程,一定要弄明白。

    下面附上C++的程序,本程序先计算第0种砝码能称出多少种重量,能称出的重量均在flag[100000]数组中做好标记,然后计算第0种砝码最多能称多大的重量,在这个基础上加入第1种砝码,然后计算第2种,直至第n种。程序最关键的地方在于29行采用CurrentWeight逐次加1试探的方式得到一个数值,然后在33行验证这个CurrentWeight是否是前一种情况中的砝码可以称出来的重量,如果是,表明新的重量值确实可以称出来,那么就把这个重量在flag[100000]数组中标记。最后统计标志数组flag[100000]种1的次数,即为所有砝码随意组合可以称出的重量。

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

int fama(int N, int weight[], int num[])
{
	int AllWeight = 0, i, j;
	for (i = 0; i < N; i++)
		AllWeight = AllWeight + weight[i] * num[i];
	int flag[100000] = {0};

	/*先计算第0种砝码能够得到的称重数量,并且计算第0种砝码最大的重量*/
	int TempWeight = 0;
	for (i = 0; i <=num[0]; i++)
	{
		flag[weight[0] * i] = 1;
	}
	TempWeight = weight[0]*num[0];
	//从此以后TempWeight将用来表示前一种砝码的最大重量
	
	i = 1;//从第1种砝码开始做
	int CurrentWeight;
	int NewWeight;
	while (i < N)
	{
		for (j = 1; j <=num[i]; j++)//第i种砝码的个数最多为num[i]
		{
			for (CurrentWeight = 0; CurrentWeight <=TempWeight; CurrentWeight++)//CurrentWeight采用试探的方式逐次加1它的大小不能大于前一种重量的最大值
			{
				NewWeight = CurrentWeight + j*weight[i];
				if (NewWeight>AllWeight) break;
				if (flag[CurrentWeight==1]&&flag[NewWeight]==0)//如果当前的这个重量可以由前一种砝码和当前砝码组合而成
				{
					flag[NewWeight] = 1;
				}
			}
		}
		TempWeight = TempWeight + num[i] * weight[i];//更新上一个砝码的最大重量
		i++;
	}

	/*统计总数量*/
	int count = 0;
	for (i = 0; i < 1000; i++) { if (flag[i] == 1) count++; }
	return count;
}

int main()
{
	int n;
	cin >> n;
	int *w = new int[n];
	int *num = new int[n];
	for (int i = 0; i < n; i++)
		cin >> w[i];
	for (int i = 0; i < n; i++)
		cin >> num[i];

	cout <<fama(n,w,num)<<endl;
	return 0;
}

    总结动态规划的思路就是–总是试图从最简单的情况开始着手找出答案,利用最简单的情况的答案计算下一种情况的答案。存在一种规律使得f(n)=f(n-1)+x的模式,使我们可以利用循环体计算n种情况。



    原文作者:动态规划
    原文地址: https://blog.csdn.net/dreamandxiaochouyu/article/details/52450688
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞