动态规划---砝码称重问题

一、算法分析

动态规划(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;  

}  


      

  • 0
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 6
    评论
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值