TOJ 3596. Watch The Movie【基础的二维费用背包问题】

           昨天训练赛有一道二维费用背包的题,结果我守着背包九讲在手边就是没做出来。事后发现其实解法背包九讲都已经讲到了,只不过自己看的不仔细罢了。以后看解题报告也好,论文也好,一定要深入思考一下,真正将这个问题搞懂并且做到可以拓展,那才是真正的学懂了。

           先说一下二维费用背包吧,我们都知道背包问题,有01背包,完全背包,多重背包。。。其中01背包是指有N件物品,每种物品只有一件,可以选择拿或者不拿,每个物品有一个重量和一个价值,找出不超过背包容量可获得的最大价值。完全背包是在01背包的基础上是指每种物品有无数件可以拿,而多重背包是指第i件物品有num [i]件可以拿。这3种背包问题的解法类似,其中01背包的转移方程核心是“拿或者不拿”,所以有转移方程: 

 

                         for(i = 1;i <= N; i++)                //N是指有N件物品

                               for(j = M;j >= cost[i]; j++)   // M是指背包容量为M

                                      dp[j] = max(dp[j] , dp[j-cost[i]] + value[i] );  //拿或者不拿

 

           完全背包的转移方程只是在01的基础上将第二重循环改为顺序,即;

 

                         for(i = 1;i <= N; i++)                //N是指有N件物品

                               for(j = cost[i];j  <= M; j++)   // M是指背包容量为M

                                      dp[j] = max(dp[j] , dp[j-cost[i]] + value[i] );  //拿或者不拿

 

           而多重背包问题需要我们将每种物品的个数按照二进制的思想拆分一下,对拆出来的每一种物品进行01背包的操作(由于每种物品按照二进制进行拆分,所以我们可以证明对每一种物品进行01背包的操作可以保证可以得到任何一种个数的组合,即我们可以保证得到最优解)在拆分物品的过程中,如果某件物品的数量*cost 大于背包容量,那么可以理解为一个完全背包,否则进行拆分,代码如下:

#include <cstdio> #include <cstring> int f[100002],v[12],num[12],cost[12]; int total,n; int max(int a,int b){ return a>b?a:b;} void OneZeroPack(int cost,int value){ int i,j; for(i = total;i >= cost; i--) f[i] = max(f[i],f[i-cost]+value); } void completePack(int cost,int weight){ int i; for(i = cost;i <= total; i++) f[i] = max(f[i],f[i-cost]+weight); } void multiPack(int cost,int weight,int amount){ if(cost*amount >= total){ completePack(cost,weight); return ; } int k = 1; while(k < amount){ OneZeroPack(k*cost,k*weight); amount -= k; k*=2; } OneZeroPack(cost*amount,amount*weight); } int main() {}

          上面这些都是一维费用的背包,即每种商品只有一种费用,就是重量或者其他,二维费用的背包是指,对于每一件物品,有两个费用,选择这个物品必须付出两种代价,对于每种代价都有一个最大值,问怎么选择物品可以获得最大的价值。

           算法:费用加了一维,只需要状态加一维即可。设dp[i][j] 表示付出两种代价分别为i和j时可以得到的最大价值,那么状态转移方程是:

          dp[i][j] = max(dp[i][j] , dp[i-a[k]][j-b[k]] + value[k]);

           这个方程和01背包或者其他背包类似,省了一维第几个物品的状态。在二维背包问题下,如果每种物品只有一件,我们就按照01背包的思路逆序循环,如果是完全背包,则顺序循环,如果是多重背包,就按照拆分的思路写,和前边都差不多。

           物品个数的限制(摘自《背包九讲》)

           有时,“二维费用”的条件是以这样一种隐含的方式给出的:最多只能取M件物品。这事实上相当于每件物品多了一种“件数”的费用,每个物品的件数费用均为 1,可以付出的最大件数费用为M。换句话说,设f[v][m]表示付出费用v、最多选m件时可得到的最大价值,则根据物品的类型(01、完全、多重)用不 同的方法循环更新,最后在f[0..V][0..M]范围内寻找答案。

 

           最后说一下很重要的初始化问题,如果没有要求恰好装满,那么初始化为0即可。如果要求恰好装满,我们在初始化时应该将个数为0时的数初始化为0,其余初始化为负无穷。因为只有背包容量为0的时候什么都不取才合法。这种初始化的思想对于一切背包问题都适用。

            说一下TOJ3596,题意很简单,有N张光盘,每张光盘有一个价钱,现在要从N张光盘中买M张,预算为L,每张光盘有一个快乐值,要求在不超过预算并且恰好买M张,使得快乐值最大。

             分析:典型的二维费用背包问题,另外一种隐含的费用为个数,每个物品的个数费用为1。要求恰好买M张表示要求恰好装满,所以初始化不是0,而是-INF。剩下的就是上面讲到的状态转移方程了。

  #include <cstdio> #include <cstring> #include <algorithm> #define max(a,b) ((a)>(b)?(a):(b)) #define INF 0x1f1f1f1f using namespace std; int dp[105][1005],cost[105],value[105]; int main() { //freopen("in.txt","r",stdin); //freopen("out.txt","w",stdout); int i,j,k,n,m,T,L; scanf("%d",&T); while(T--){ scanf("%d%d%d",&n,&m,&L); for(i = 1;i <= n; ++i) scanf("%d%d",&cost[i],&value[i]); for(i = 1;i <= m; ++i) for(j = 0;j <= L; ++j){ dp[i][j] = -INF; dp[0][j] = 0; } for(k = 1;k <= n; ++k) for(j = m;j >= 1; j--) for(i = L;i >= cost[k]; --i){ dp[j][i] = max(dp[j][i],dp[j-1][i-cost[k]]+value[k]); } int ans = 0; for(i = 1;i <= L; ++i) if(dp[m][i] > ans) ans = dp[m][i]; printf("%d/n",ans); } }

你可能感兴趣的:(算法)