dp初步(poj1276)

来源:互联网 发布:男士内裤品牌 知乎 编辑:程序博客网 时间:2024/05/16 23:46

题意:

有各种不同面值的货币,每种面值的货币有不同的数量,请找出利用这些货币可以凑成的最接近且小于等于给定的数字cash的金额。

 

初始思路:

多重背包问题,第i种面额d[i] n[i]+1种选择方案

可以转化为01背包问题处理

转化的大概思路就是把每种面值乘以其不同的个数,把得到的不同金额作为一件新的独一无二的货币,但是这样存在两个问题,一是 d[i]*ki可能等于 d[j]*kj  ,其中kin[i]kjn[j],二是这样做一定TLE超时

所以:

首先解决存储问题,cash上限10W,推荐用new,最后记得delete就是了

 

本题输入有4个部分:

cash  //背包容量(最大可取金额)

  //物品种数(面额种数)

n[i]i种物品的个数(第i种面额的数量)

d[i]i种物品的价值(第i种面额的价值)

如果把一个金额看为一种状态,那么一共有0~cash种状态

显然其中可能会发生的状态范围为min{w[i] | 1<=i<=n[i]} ~ Cash

那么可以建立一个状态数组dp[cash+1]

其中dp[j]记录的是“最接近状态j,且<=j”的状态值,即dp[j] <=j

J越接近dp[j]dp[j]的解越优,最理想是dp[j]=j

需要注意的是,dp[j]为了说明的是状态j可以发生,但不会理会dp[j]怎样发生

例如有31元,13

那么我们既可以认为dp[3]=3是通过取31元得到,也可以认为dp[3]是通过取13元得到的,但无论怎样得到,dp[3]=3都会发生,

再需要注意的是,dp[j]的状态值会累积

再形象举例说明:例如有13,cash=4

那么根据前面的说明,自然有dp[3]=dp[4]=3,就是说状态3、状态4都可以通过取13元发生,一旦发生,这个状态值3就会被保有在当前的状态dp[4]这其实是起到一个优化作用,后面详细解释

再接上例,当我们增加一个条件“有31元”,那么在已经被保存的前提“dp[4]=3”下,我们只需要通过取11元,就能得到比dp[4]=3的更优解dp[4]=4,但此时我们完全不用理会dp[4]=3是怎样来的,我们只需要知道dp[4]=3一定会出现就足够了

 

然后说说刚才提到的“优化问题”

利用01背包的知识,不难理解状态方程 dp[j]=dp[ j-c[i] ]+w[i]

优化是因为状态值被记录了,就是说我们在取得下一个货币i之前,当前的状态为j-c[i]

一旦我们选取货币i,就会得到状态(j-c[i]+c[i],即状态j。且状态j的状态值dp[j]会加上w[i]。至于dp[j]原来值是多少,就无需理会,因为dp[j]的值是累积下来的,同样本次加上w[i]后,只要dp[j]值未到最优,它同样会成为下一次的累加值。

这样每次都直接调用前一次已获得的状态值,就可以节省一堆搜索的时间,这是DFSBFS办不到的,也是动态规划的优点。

 

接下来说说计数器count[j]

在我的程序中,每更换一次面额,计数器会清零,这样做是为了以面额i的价值w[i]为权,根据选取该面额的个数,对状态值dp[j]进行w[i]的整数倍分割,这样就能得到对于每组状态dp[w[i]*k]dp[w[i]*k+1] (0<=k<=n[i])之间,在当前面额w[i]下的最优状态值。

例如有 33

自然dp[0]=dp[1]=dp[2]=0count[0]= count[1]= count[2]=0这是因为最小面额为3

不难得到dp[3]=dp[4]=dp[5]=3count[3]= count[4]= count[5]=1这是因为面额3元不可分,这3个状态的最优值就是取13

dp[6]=dp[7]=dp[8]=6 , count[6]= count[7]= count[8]=2这3个状态的最优值就是取23

dp[9]=dp[10]=dp[11]=dp[…]=9 ,count[9]= count[10]= count[[11]= count[…]=3 最多只有33元,以后的的状态的最优值均为9

 

最后说说用memset函数初始化的问题

程序中也说得很清楚了,由于是动态空间,不要用sizeof,要人为计算空间大小,记得+1

#include<iostream>#include <cstring>using namespace std;int main(){    int N;   //物面额种数    int cash;  //最大可取金额    while(cin>>cash>>N)    {        int* n=new int[N+1];  //n[i]第i种面额的数量        int* w=new int[N+1];  //w[i]第i种面额的价值        int* c=new int[N+1];  //c[i]第i种面额消耗的价值        int* dp=new int[cash+1];  //dp[j]记录的是 当前最接近状态j且<=j的值,dp值会累积        int* count=new int[cash+1];//计数器,限制面额的选取个数        for(int i=1;i<=N;i++)        {            cin>>n[i]>>w[i];//输入不同面额和不同面额的数量            c[i]=w[i];    //本题的单个物品的“体积”等于其“价值”        }        memset(dp,0,4*(cash+1));  //由于dp申请的是动态内存,用sizeof计算长度会出错                                  //这里要用 类型大小*个数,这里为 int*(cash+1) , int大小为4        for(int i=1;i<=N;i++)        {            memset(count,0,4*(cash+1));  //每更换一次面额,计数器清零            for(int j=w[i];j<=cash;j++)      //对于第i种货币,其面额d[i]~cash间任一个状态都可能发生                if(dp[j]<dp[j-c[i]]+w[i] && count[j-c[i]]<n[i]) //count[j-c[i]]<n[i]                {                                               //取某种面额前,必须保证这次操作之前所取该种面额数量不为0,即还存在                    dp[j] = dp[j-c[i]]+w[i];    //选取第i个物品后,背包容量(允许取的最大金额)减少c[i]                    count[j]=count[j-c[i]]+1;   //对于当前状态j,第i种面额被抽了count[j]次                }        }        cout<<dp[cash]<<endl;        delete n;        delete w;        delete c;        delete dp;        delete count;    }    return 0;}


0 0