• 0/1背包


    【问题描述】

    一个旅行者有一个最多能用m公斤的背包,现在有n件物品,它们的重量分别是W1,W2,...,Wn,它们的价值分别为C1,C2,...,Cn.若每种物品只有一件求旅行者能获得最大总价值。

    【输入格式】

    第一行:两个整数,M(背包容量,M<=200)和N(物品数量,N<=30); 第2..N+1行:每行二个整数Wi,Ci,表示每个物品的重量和价值。

    【输出格式】

    仅一行,一个数,表示最大总价值。

    【样例输入】package.in

    10 4 2 1 3 3 4 5 7 9

    【样例输出】package.out

    12


    f[i][v]=max{ f[i-1][v] , f[i-1][v-w[i]]+c[i] }。

    初值f[0][0]=0,其余为负无穷,目标:max{ f [n][j] } 0<=j<=m


     

    (优化空间复杂度)

    通过DP的状态转移方程,发现每一阶段i的状态只与上一阶段i-1的状态有关。在这种情况下,我们可以使用称为“滚动数组”的优化方法,降低空间的开销。 


    (继续优化空间复杂度) 

    以上方法的时间和空间复杂度均为O(N*V),其中时间复杂度基本已经不能再优化了,但空间复杂度却可以优化到O(V)。   

    先考虑上面讲的基本思路如何实现,肯定是有一个主循环i=1..N,每次算出来二维数组f[i][0..V]的所有值。那么,如果只用一个数组f [0..V],能不能保证第i次循环结束后f[v]中表示的就是我们定义的状态f[i][v]呢?f[i][v]是由f[i-1][v]和f[i-1][v-w[i]]两个子问题递推而来,能否保证在推f[i][v]时(也即在第i次主循环中推f[v]时)能够得到f[i-1][v]和f[i-1][v-w[i]]的值呢?事实上,这要求在每次主循环中我们以v=V..0的逆序推f[v],这样才能保证推f[v]时f[v-w[i]]保存的是状态f[i-1][v-w[i]]的值。

    伪代码如下:   

    for i=1..N    

    for v=V..0      

    f[v]=max{f[v],f[v-w[i]]+c[i]};   

    其中f[v]=max{f[v],f[v-w[i]]+c[i]}相当于转移方程f[i][v]=max{f[i-1][v],f[i-1][v-w[i]]+c[i]},因为现在的f[v-w[i]]就相当于原来的f[i-1][v-w[i]]。如果将v的循环顺序从上面的逆序改成顺序的话,那么则成了f[i][v]由f[i][v-w[i]]推知,与本题意不符,但它却是另一个重要的完全背包问题最简捷的解决方案,故学习只用一维数组解01背包问题是十分必要的。


    【解法一】

    设f[i][v]表示前i件物品,总重量不超过v的最优价值,则f[i][v]=max(f[i-1][v-w[i]]+c[i],f[i-1][v]) ;f[n][m]即为最优解

    给出程序:

     1 #include<cstdio>
     2 using namespace std;
     3 const int maxm = 201, maxn = 31;
     4 int m, n;
     5 int w[maxn], c[maxn];
     6 int f[maxn][maxm]; 
     7 
     8 int max(int x,int y)  { x>y?x:y;}    //求x和y最大值
     9 
    10 int main(){
    11     scanf("%d%d",&m, &n);         //背包容量m和物品数量n
    12     for (int i = 1; i <= n; i++)         //在初始化循环变量部分,定义一个变量并初始化
    13       scanf("%d%d",&w[i],&c[i]);    //每个物品的重量和价值
    14     for (int i = 1; i <= n; i++)         // f[i][v]表示前i件物品,总重量不超过v的最优价值
    15         for (int v = m; v > 0; v--)
    16             if (w[i] <= v)  f[i][v] = max(f[i-1][v],f[i-1][v-w[i]]+c[i]);
    17                else  f[i][v] = f[i-1][v];
    18      printf("%d",f[n][m]);               // f[n][m]为最优解
    19      return 0;
    20 }

    使用二维数组存储各子问题时方便,但当maxm较大时,如maxm=2000时不能定义二维数组f,怎么办,其实可以用一维数组。

    【解法二】

    本问题的数学模型如下:设 f[v]表示重量不超过v公斤的最大价值, 则f[v]=max{f[v],f[v-w[i]]+c[i]} ,当v>=w[i],1<=i<=n 。

    程序如下:

     1 #include<cstdio>
     2 using namespace std;
     3 
     4 const int maxm = 2001, maxn = 31;
     5 int m, n;
     6 int w[maxn], c[maxn];
     7 int f[maxm]; 
     8 int main(){
     9     scanf("%d%d",&m, &n);           //背包容量m和物品数量n
    10     for (int i=1; i <= n; i++)
    11         scanf("%d%d",&w[i],&c[i]);   //每个物品的重量和价值
    12    
    13     for (int i=1; i <= n; i++)              //设f(v)表示重量不超过v公斤的最大价值
    14         for (int v = m; v >= w[i]; v--)
    15             if (f[v-w[i]]+c[i]>f[v])
    16                 f[v] = f[v-w[i]]+c[i];
    17 printf("%d",f[m]);                           // f(m)为最优解
    18 return 0;
    19 }

    总结

    01背包问题是最基本的背包问题,它包含了背包问题中设计状态、方程的最基本思想,另外,别的类型的背包问题往往也可以转换成01背包问题求解。故一定要仔细体会上面基本思路的得出方法,状态转移方程的意义,以及最后怎样优化的空间复杂度。


    这个博客讲得超好的呢!

  • 相关阅读:
    算法训练 P1103
    算法训练 表达式计算
    算法训练 表达式计算
    基础练习 时间转换
    基础练习 字符串对比
    Codeforces 527D Clique Problem
    Codeforces 527C Glass Carving
    Codeforces 527B Error Correct System
    Codeforces 527A Glass Carving
    Topcoder SRM 655 DIV1 250 CountryGroupHard
  • 原文地址:https://www.cnblogs.com/ljy-endl/p/11260335.html
Copyright © 2020-2023  润新知