zoukankan      html  css  js  c++  java
  • 简单背包问题

    背包问题
    应用场景
    给定 $n$ 种物品和一个背包。物品 $i$ 的重量是 $w_i$ ,其价值为 $v_i$ ,背包的容量为C。应该如何选择装入背包中的物品,使得装入背包的总价值最大?
    *01 背包

    *01 背包特点:
      给定 $n$ 种物品和一个背包 ( 每个物品只能选取一个)。物品$i$ 的重量是$w[i]$,其价值为$v[i]$,背包的容量为C。应该如何选择装入背包中的物品,使得装入背包中的物品的总价值最大?
    *状态:
      $dp[i][j]$ 表示在只能从 $1-i$ 个物品中选择物品并且背包容量大小为 $j$ 的情况下,所能获得的最大价值。
    *限制条件
      • 只能选择物品 1 ~ i
      • 选择物品的总重量不能超过 $j$
    *状态转移方程:
      $dp[i][j] = max(dp[i-1][j], dp[i-1][j-w[i]] + v[i])$;

    *实现:

    1)初始化:

    1 int n,m; //n表示物品个数,m表示背包容量
    2 for (int i = 0 ;i<= m ;i++)
    3     dp[0][i] = 0;

    2)-1二维数组实现:

    1 for (int i = 1;i <= n ;i++){
    2     for (int j = 0 ;j<=m ;j++){
    3     dp[i][j] = dp[i-1][j];
    4     if (j-w[i]>=0)
    5         dp[i][j] = max(dp[i][j],dp[i-1][j-w[i]]+v[i]);
    6     }
    7 }

    2)-2一维数组实现

    1 for (int i = 1;i <= n ;i++){
    2     for (int j = m ;j>=0 ;j--){
    3         if (j-w[i]>=0)
    4             dp[j] = max(dp[j],dp[j-w[i]]+v[i]);
    5     }
    6 }

    *完全背包

    *完全背包特点

      给定 $n$ 种物品和一个背包 ( 每个物品选取无限个)。物品 $i$ 的重量是 $w[i]$,其价值为$v[i]$,背包的容量为$C$。应该如何选择装入背包的物品,是的装入背包中物品的总价值最大?
    *状态

      $dp[i][j]$ 表示在只能从 $1-i$ 个物品中选择物品并且背包容量大小为 $j$ 的情况下,所能获得的最大价值。
    *限制条件

      • 只能选择物品 1 ~ i
      • 选择物品的总重量不能超过 j

    *状态转移方程

      $dp[i][j] = max(dp[i-1][j], dp[i][j-w[i]] + v[i])$;

    *实现

    1)初始化

    1 int n,m; //n表示物品个数,m表示背包容量
    2 for (int i = 0 ;i<= m ;i++)
    3     dp[0][i] = 0;

    2)-1二维数组实现

    1 for (int i =1;i <= n;i++){
    2     for (int j = 0;j <= m;j++){
    3         dp[i][j]=dp[i-1][j];
    4         if (w[i]<=j)
    5             dp[i][j]=max(dp[i][j],dp[i][j-w[i]]+v[i]);
    6     }
    7 }

    2)-2一维数组实现

    1 for (int i =1;i <= n;i++){
    2     for (int j = 0;j <= m;j++){
    3         if (d-w[i]>=0)
    4         dp[j]=max(dp[j],dp[j-w[i]]+v[i]);
    5     }
    6 }

    *多重背包

    *多重背包特点

      给定 $n$ 种物品和一个背包 ( 每个物品选取有限个)。物品 $i$ 的重量是 $w[i]$ ,其价值为$v[i]$,每个物品可以取$k[i]$个,背包的容量为C。应该如何选择装入背包中的物品使得装入背包中的物品总价值最大?

    *状态

      $dp[i][j]$ 表示在只能从 $1-i$ 个物品中选择物品并且背包容量大小为 $j$ 的情况下,所能获得的最大价值

    *限制条件

      • 只能选择物品 1 ~ i
      • 选择物品的总重量不能超过 j

    *状态转移

      $dp[i][j] = max(dp[i-1][j], dp[i-1][j-w[i]] + v[i], dp[i-1][j-w[i]*2] + v[i]*2,...)$

    *实现

    1)初始化

    2)-1二维数组实现

     1 for (int i = 1;i <= n ;i++){
     2     for (int j = 0 ;j<=m ;j++){
     3         dp[i][j] = dp[i-1][j];
     4         for (int z = 1 ; z<=k[i]; z++)
     5             if (j-w[i]*z>=0)
     6                 dp[i][j] = max(dp[i][j],dp[i-1][j-z*w[i]]+z*v[i]);
     7             else
     8                 break;
     9     }
    10 }

    2)-2一维数组实现

    1 for (int i = 1;i <= n ;i++){
    2     for (int j = m ;j>=0 ;j--){
    3         for (int z = 1; z<=k[i] ;z++)
    4             if (j-w[i]*z>=0)
    5                 dp[j] = max(dp[j],dp[j-z*w[i]]+z*v[i]);
    6             else
    7                 break;
    8     }
    9 }

    *多重背包问题转化为 01 背包问题
    1. p 以下的数字可否由若干不同数字通过组合得到
      (a) $2^n$- 1 $n$ == 7 二进制: 111111 000001、000010、000100、001000、010000、100000
      (b) $k$ + $2^n$ -1 < $2^{n+1}$ -1 $n$ == 7
    • $2^n$ -1 及以下的数字可以由以下数字表示 000001、000010、000100、001000、010000、100000
    • 大于 $2^n$ -1 的数字 $k$ 加上以上 7 个数字组成的数字可以表示任何大于$2^n$ - 1小于等于k+$2^n$-1的数字
    2. 代码实现

     1 //num[i] 表示第i种硬币可以取多少次
     2 //w[j] 表示转后为01背包后 只能取一次的硬币的价值与重量
     3 int totlW = 0;
     4 memset(w,0,sizeof w);
     5 for (int i = 0 ;i< 6; i ++){
     6     for(int j=1; j<=num[i]; j<<=1){
     7         w[totlW++]=j*(i+1);
     8         num[i]-=j;
     9         }
    10     if(num[i]>0)
    11     w[totlW++]=num[i]*(i+1);
    12 }

    背包问题
    HDU 2602
    POJ 2063
    POJ 1787
    UVA 674
    UVA 147

    第一题是01背包的板子题,就直接放代码了:

     1 #include <iostream>
     2 #include <cstdio>
     3 #include <algorithm>
     4 #include <cmath>
     5 #include <cstring>
     6 using namespace std;
     7 int t;
     8 int n,m;
     9 int val[10000],w[10000],f[100000];
    10 int main ()
    11 {
    12     scanf ("%d",&t);
    13     while (t--)
    14     {
    15         scanf ("%d%d",&n,&m);
    16         memset(f,0,sizeof(f));
    17         for (int i = 1;i <= n;i++)
    18             scanf ("%d",&val[i]);
    19         for (int i = 1;i <= n;i++)
    20             scanf ("%d",&w[i]);
    21         for (int i = 1;i <= n;i++)
    22             for (int j = m;j >= w[i];j--)
    23                 f[j]=max(f[j],f[j-w[i]]+val[i]);
    24         printf ("%d
    ",f[m]);
    25     }
    26     return 0;
    27 }

    第二道题是完全背包,因为每个债券都是1000的倍数,所以我们可以直接把总钱数和债券的数额都直接除以1000.因为每年都会有利息,所以每年的总钱数会增加,求出每一年的最大利益,总钱数加上最大利益就是下一年的总钱数。所以,我们只需要对于每一年的总钱数求一个完全背包,每年更新总钱数就好。代码如下:

     1 #include <iostream>
     2 #include <cstdio>
     3 #include <algorithm>
     4 #include <cmath>
     5 #include <cstring>
     6 using namespace std;
     7 const int N = 100100;
     8 int dp[N];
     9 int num[N], val[N];
    10 int main()
    11 {
    12     int t;
    13     scanf("%d", &t);
    14     while(t--)
    15     {
    16         int money,year,d;
    17         scanf("%d %d", &money, &year);
    18         scanf("%d", &d);
    19         for(int i=0;i<d;i++)
    20         {
    21             scanf("%d %d", &num[i], &val[i]);
    22             num[i]/=1000;
    23         }
    24         int sum=money;
    25         for(int i=0;i<year;i++)
    26         {
    27             memset(dp,0,sizeof(dp));
    28             money=sum;
    29             money/=1000;
    30             for(int j=0;j<d;j++)
    31             {
    32                 for(int k=num[j];k<=money;k++)
    33                 {
    34                     dp[k]=max(dp[k-num[j]]+val[j],dp[k]);
    35                 }
    36             }
    37             sum+=dp[money];
    38         }
    39         printf("%d
    ",sum);
    40     }
    41     return 0;
    42 }

     第三题是完全背包+路径输出,$dp[j]$表示总价格为$j$时最多可以有多少个硬币,但是需要维护一下硬币的个数,实际上在01背包中,我们对所有状态都更新了一次答案,这里我们等于是对4种硬币各跑一次背包,维护一下使用次数即可。$used[j]$表示总价格为j时用了多少个某个硬币。$pre[j]$记录方案,表示j出现更有的答案时是由哪个状态转移来的。最后遍历一下输出答案就好了。

     1 #include <stdio.h>
     2 #include <string.h>
     3 using namespace std;
     4 int dp[10005], used[10005], pre[10005], coin[4] = {1, 5, 10, 25}, num[4], ans[26];
     5 int main(){    
     6     int p;    
     7     while(scanf("%d %d %d %d %d", &p, &num[0], &num[1], &num[2], &num[3]) != EOF){        
     8         if(p + num[0] + num[1] + num[2] + num[3] == 0) return 0;        
     9         for(int i = 0; i <= p; ++i){            
    10             dp[i] = -1e9;        
    11         }        
    12         memset(pre, 0, sizeof(pre));        
    13         pre[0] = -1;        
    14         dp[0] = 0;        
    15         for(int i = 0; i < 4; ++i){            
    16             memset(used, 0, sizeof(used));            
    17             for(int j = coin[i]; j <= p; ++j){                
    18                 if(dp[j] < dp[j - coin[i]] + 1 && used[j - coin[i]] < num[i]){                    
    19                     used[j] = used[j - coin[i]] + 1;                    
    20                     dp[j] = dp[j - coin[i]] + 1;                    
    21                     pre[j] = j - coin[i];                
    22                 }            
    23             }        
    24         }        
    25         if(dp[p] <= 0){            
    26             printf("Charlie cannot buy coffee.
    ");            
    27             continue;        
    28         }        
    29         memset(ans, 0, sizeof(ans));        
    30         while(1){            
    31             if(pre[p] == -1) break;            
    32             ans[p - pre[p]]++;            
    33             p = pre[p];        
    34         }        
    35         printf("Throw in %d cents, %d nickels, %d dimes, and %d quarters.
    ", ans[1], ans[5], ans[10], ans[25]);    
    36     }
    37 }

    第四题一个简单的动态规划,比如要求55的组成方案数,必须要知道55-1=54的方案数 和 55-5=50的方案数,和50-10=45的方案数和55-25=30的方案数和55-50=5的方案数,所以dp以此类推,每次更新dp。

     1 #include <cstring>
     2 #include <iostream>
     3 #include <cstdio>
     4 using namespace std;
     5 int f[10000];
     6 int main ()
     7 {
     8     int v;
     9     while(cin>>v){
    10         memset(f,0,sizeof(f));
    11         int c[6]={0,1,5,10,25,50};
    12         f[0]=1;
    13         for(int i = 1; i <= 5; ++i)
    14             for(int j = c[i]; j <= v; ++j)
    15             {
    16                 f[j] = f[j]+f[j-c[i]];
    17             }
    18         cout << f[v]<<endl;
    19     }
    20     return 0;
    21  }
  • 相关阅读:
    asking邱宝裕
    一文深度解读量化交易(下)
    一文深度解读量化交易(上)
    关于日内波段交易系统:一个期货高手的交易思路独白
    财务报表分析(张新民教授)-第一章笔记
    定增游戏(二)
    leetcode -- Restore IP Addresses
    leetcode -- Minimum Window Substring
    leetcode -- Maximal Rectangle TODO O(N)
    leetcode -- Unique Binary Search Trees II
  • 原文地址:https://www.cnblogs.com/very-beginning/p/12303816.html
Copyright © 2011-2022 走看看