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

    01背包问题

    来源

    https://www.acwing.com/problem/content/2/

    Description

    有 N 件物品和一个容量是 V 的背包。每件物品只能使用一次。
    
    第 i 件物品的体积是 vi,价值是 wi。
    
    求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。
    输出最大价值。
    

    Input

    第一行两个整数,N,V,用空格隔开,分别表示物品数量和背包容积。
    
    接下来有 N 行,每行两个整数 vi,wi,用空格隔开,分别表示第 i 件物品的体积和价值。
    

    Output

    输出一个整数,表示最大价值。
    

    数据范围

    0<N,V≤1000
    0<vi,wi≤1000
    

    Sample Input

    4 5
    1 2
    2 4
    3 4
    4 5
    

    Sample Output

    8
    

    分析问题:

    1. 01背包问题的特点:每个物品只有一件,只有取或者不取两种状态

    2. 由于背包问题是dp的一个大类,所以我们也这里采用dp的逻辑框架来思考问题:

      • 状态表示:dp[i][j]表示在包括前i种物品,容积为j情况下的最大价值

      • 状态转移方程:dp[i][j]=max(dp[i-1][j],dp[i-1][j-w[i]]+v[i])

        MOOC的陈越老师说过越简单的算法包含的信息量往往越大,第一次看到这个方程先别晕,我们来慢慢将它解释清楚.

        由于状态转移方程代表的两个状态之间的联系,所以让我们思考一下到dp[i][j]有哪些“路径”:

        • 显然每个物品的状态都有拿/不拿两种状态
        • dp[i-1][j]:先思考不拿的情况,显然dp[i][j]的状态可以直接由dp[i-1][j]得到
        • dp[i-1][j-w[i]]+v[i]:再思考的情况,此时我们需要先保证对应的体积是满足j-w[i]>0的,对应的上一个状态也不应该是简单的dp[i-1][j-1],而是dp[i-1][j-w[i]],此时再加上当前物品的价值即可

        一个明显的事实是转态转移的过程应该是从无到有,对应的下标也应该是从小到大的,

        所以我们这里也是从对应的这两个维度去讨论.

      • 初始化:由于dp[0][0]代表的是0种物品,0体积下的最大价值,所以初值应该为0

      #include <stdio.h>
      #include <stdlib.h>
      #include <iostream>
      #include <algorithm>
      #include <string.h>
      #define INF 0x3f3f3f3f
      
      using namespace std;
      
      const int N=1e3;
      int dp[N+10][N+10],w[N],v[N];  //dp[i][j]表示到第i种商品,恰好在j体积下的最大价值
      
      int main()
      {
          int n,m;
          while(~scanf("%d %d",&n,&m)){
              memset(dp,0,sizeof dp);
              for(int i=1;i<=n;i++){
                  scanf("%d %d",&w[i],&v[i]);
              }
              for(int i=1;i<=n;i++){
                  for(int j=1;j<=m;j++){  //由于dp的定义,我们显然也要保证j<w[i]状态的初始化,因为我们在机型dp[i-1][j-w[i]]的时候很可能要使用到对应的状态
                      dp[i][j]=dp[i-1][j];
                      if(j>=w[i]){
                          dp[i][j]=max(dp[i][j],dp[i-1][j-w[i]]+v[i]);  //两种状态:拿,或者不打
                      }
                  }
              }
              cout << dp[n][m] << endl;
          }
      }
      
    3. 思考一下在dp的框架下如何优化这个问题,我们是否可以将状态方程由二维转为一维:

      • 观察上式,我们可以看出dp[i][j]i维上的状态始终只与i-1维有关,所以我们可以对其进行优化

      • 状态表达式:dp[j]表示i体积下的最大价值

      • 状态转移方程:dp[j]=max(dp[j],dp[j-w[i]]+v[i])

        注:关于这里为什么dp[j],大家可以对二维状态表达式方法做一个等价变换以帮助理解

      • 初始化:dp[0]=0

      • 注意点:和上一题不同的是,在二维存储的状态下,我们可以通过i-1保证对于第i件的一次取或不取的操作.但是在一维状态,我们无法确保当前操作的dp[j]是否已包含了第i件物品.

        我们以一次初始化为例查看这个问题(字丑,勿怪):

        image-20210122102805099

        解决:将j从大到小枚举

        image-20210122103103387

      #include <stdio.h>
      #include <stdlib.h>
      #include <iostream>
      #include <algorithm>
      #include <string.h>
      #define INF 0x3f3f3f3f
      
      using namespace std;
      
      const int N=1e3;
      int dp[N+10],w[N],v[N];  //dp[i]表示到i体积下的最大价值
      
      int main()
      {
          int n,m;
          while(~scanf("%d %d",&n,&m)){
              memset(dp,0,sizeof dp);
              for(int i=1;i<=n;i++){
                  scanf("%d %d",&w[i],&v[i]);
              }
              for(int i=1;i<=n;i++){
                  for(int j=m;j>=w[i];j--){  //保证了0/1的特性
                      dp[j]=max(dp[j],dp[j-w[i]]+v[i]);  //注意到在求max的时候,体积维度的值始终是不需要通过-1初始化的
                  }
              }
              cout << dp[m] << endl;
          }
      }
      
  • 相关阅读:
    OC中数组的使用方法
    fuel Explain
    OpenStack images
    linux dd实现磁盘完整全盘镜像备份backup,恢复recover(restore)
    linux大事件集
    Ruiy classicsQuotations
    OpenSuSE zypper OpenStack Icehouse repoAdd
    OpenSuSE zypper repo及Desktop媒体播放器设置 for OpenSuSE12.
    OpenSuSE查看指定软件包是否安装(OpenSuSE使用RPM作为默认的软件包维护管理工具)
    OpenStack开启sshd
  • 原文地址:https://www.cnblogs.com/Arno-vc/p/14311938.html
Copyright © 2011-2022 走看看