zoukankan      html  css  js  c++  java
  • 【背包问题】【出来混总是要还的...】总结+入门练手题

    花了一晚上加一早上研究背包,唉一大把年纪了才狠下心弄dp也确实说不过去的......

    背包入门当然还是看背包九讲(链接很多,没找到原作的,就随便贴一个链接了...),我再扯也是班门弄斧,只是贴一些摘要以及写代码时候的总结吧。

    01背包:有N件物品和一个容量为V的背包。第i件物品的体积是v[i],价值是val[i],每种只有一件。求解将哪些物品装入背包可使价值总和max_val最大。

    max_val[i][j] -->  dp[i][j] : 从前i个物品中选择重量不超过j的物品时的最大价值;

    max_val[i][0] = max_val[0][j] = 0; ( i~{1,N}, j~{0,V}; )

    只考虑第i件物品的策略
    max_val[i][j] =  ① max_val[i-1][j];                      j > v[i]
                ② max{ max_val[i-1][j], max_val[i-1][j-v[i]]+val[i] }; j <= v[i];

    ②的判断过程:1) 计算不放入该物品时该重量的最大价值;
                        2) 计算当前物品的价值 + 可以容纳的剩余重量的最大价值;
                        3) 找到二者之中的最大值。
    解决了所有的子问题之后,返回max_val[N][W]的值——N件物品重量为W时最大价值;

    状态转移方程也可以用一维数组表示(切记01背包是逆序求解,稍后的完全背包是顺序求解):

    1 void zero_one_pack(int cost, int value)  
    2 {  
    3     for(int i = sum;i >= cost;i--)  
    4         dp[i] = max(dp[i],dp[i-cost]+value);  
    5 }
    6 for(int i = 1; i <= n; i++)
    7 {
    8     zero_one_pack(v[i], val[i]);
    9 }    

    完全背包: 有N种物品和一个容量为V的背包,每种物品都有无限件可用。第i种物品的体积是v[i],价值是val[i]。求将哪些物品装入背包可使这些物品的总体积不超过背包容量,且价值总和最大。

    基本跟01背包一个思路,只不过多了些变化:

    max_val[i][j] -->  dp[i][j] : 从前i个物品中选择重量不超过j的物品时的最大价值;

    max_val[i][0] = max_val[0][j] = 0; ( i~{1,N}, j~{0,V}; )

    只考虑第i件物品的策略
    max_val[i][j] =  ① max_val[i-1][j];                      j > v[i]
                ② max{ max_val[i-1][j-k*v[i]]+k*val[i] | k >= 0 };

                            =max{ max_val[i-1][j], max{max_val[i-1][j-k*v[i]]+k*val[i] | k >= 1} };

              =max{ max_val[i-1][j], max{max_val[i-1][(j-v[i])-k*v[i]]+ val[i] + k*val[i] | k >= 0} };

              =max{ max_val[i-1][j], max{max_val[i-1][(j-v[i])-k*v[i]] + k*val[i] | k >= 0} + val[i] };

                  =max{ max_val[i-1][j], max_val[i][j-v[i]] + val[i] };

    注意与01背包的区别--01背包的第一维是i-1,而完全背包的第一维是i;因此完全背包的一维数组表示时是顺序求解;

     1 void complete_pack(int cost,int value)  
     2 {  
     3     for(int i = cost; i <= sum; i++)  
     4         dp[i] = max(dp[i], dp[i-cost]+value);  
     5 }
     6     
     7 for(int i = 1; i <= n; i++)
     8 {
     9     complete_pack(v[i], val[i]);
    10 }

    多重背包:有N种物品和一个容量为V的背包。第i种物品最多有n[i]件可用,每件体积是v[i],价值是val[i];
    ①基本算法

    max_val[i][j] =  ① max_val[i-1][j];                      j > v[i]
                ② max{ max_val[i-1][j-k*v[i]]+k*val[i] | 0<=k<=n[i] };

                            =max{ max_val[i-1][j], max{max_val[i-1][j-k*v[i]]+k*val[i] | 1<=k<=n[i]} };

              =max{ max_val[i-1][j], max{max_val[i-1][(j-v[i])-k*v[i]]+ val[i] + k*val[i] | 0<=k<n[i]} };

              =max{ max_val[i-1][j], max{max_val[i-1][(j-v[i])-k*v[i]] + k*val[i] | 0<=k<n[i]} + val[i] };

                  =max{ max_val[i-1][j], max_val[i][j-v[i]] + val[i] };

    复杂度是O(V*Σn[i])


    ② 转化为01背包问题:把每种物品的n[i]件看成n[i]件01背包中不同的物品,则得到了物品数为Σn[i]的01背包问题,直接求解,复杂度也是O(V*Σn[i])

    eg. hdu-2191

     1 scanf("%d %d",&n,&m);
     2 memset(dp,0,sizeof(dp));
     3 for(i=0;i<m;i++)
     4 {
     5     scanf("%d %d %d",&p,&h,&c);
     6     while(c--)
     7     {
     8         for(j=n;j>=p;j--)    dp[j]=max(dp[j],dp[j-p]+h);
     9     }    
    10 }
    11 printf("%d
    ",dp[n]);
    hdu_2191主要代码

    ③转化为01背包+O(V*Σlogn[i])(二进制思想)
    将第i种物品分成若干件物品,其中每件物品有一个系数,这件物品的费用和价值均是原来的费用和价值乘以这个系数。使这些系数分别为1,2,4,...,2^(k-1),n[i]-2^k+1,且k是满足n[i]-2^k+1>0的最大整数。例如,如果n[i]为13,就将这种物品分成系数分别为1,2,4,6的四件物品。

     1 #include<iostream>
     2 #include<cstdio>
     3 #include<cstdlib>
     4 #include<cstring>
     5 #include<algorithm>
     6 using namespace std;
     7 const int maxn = 1000005;
     8 int n, V; //总体积
     9 
    10 int dp[maxn];
    11 int v[maxn]; //体积
    12 int w[maxn]; //价值
    13 int c[maxn]; //数量
    14 void Zero_One_Pack(int singleW, int singleV)
    15 {
    16     for(int v = V; v >= singleV; v--)
    17     {
    18         dp[v] += dp[v-singleV];
    19     }
    20     return ;
    21 }
    22 
    23 void Complete_Pack(int singleW, int singleV)
    24 {
    25     for(int v = singleV; v <= V; v++)
    26     {
    27         dp[v] += (dp[v-singleV]);
    28     }
    29     return ;
    30 }
    31 
    32 void MultiplePack(int i)
    33 {
    34     int amount = c[i];
    35     if(v[i]*amount >= V) //每件的体积乘以数量大于总背包体积时,用完全背包
    36     {
    37         Complete_Pack(w[i], v[i]);
    38         return ;
    39     }
    40     int k = 1;
    41     while(k < amount)    //01背包二进制优化
    42     {
    43         Zero_One_Pack(k*w[i], k*v[i]);
    44         amount -= k;
    45         k <<= 1;
    46     }
    47     Zero_One_Pack(amount*w[i], amount*v[i]);
    48     return ;
    49 }
    50 
    51 int main()
    52 {
    53     scanf("%d%d", &n, &V);
    54     for(int i = 1; i <= n; i++)
    55     {
    56         scanf("%d%d%d", &w[i]&v[i]&c[i]);
    57     }
    58     memset(dp, 0, sizeof(dp));
    59     for(int i = 1; i <= n; i++)
    60     {
    61         MultiplePack(i);
    62     }
    63     printf("%d
    ", dp[V]);
    64     return 0;
    65 }
    多重背包转化为01背包的二进制优化


    ④O(VN)解法,单调队列(待续..)

    混合三种背包问题:有的物品只可以取一次(01背包),有的物品可以取无限次(完全背包),有的物品可以取的次数有一个上限(多重背包),应该怎么求解呢?
    ①、如果只有两类物品:一类物品只能取一次,另一类物品可以取无限次,那么只需在对每个物品应用转移方程时,根据物品的类别选用顺序(完全)或逆序(01)的循环即可,复杂度是O(VN)。

    伪代码如下:

    1 for i=1..N
    2     if 第i件物品是01背包
    3         for j=V..cost
    4             f[j]=max{f[j],f[j-v[i]]+w[i]};
    5     else if 第i件物品是完全背包
    6         for j=v[i]..V
    7             f[j]=max{f[j],f[j-v[i]]+w[i]};

    ②、如果再加上有的物品最多可以取有限次,那么原则上也可以给出O(VN)的解法:遇到多重背包类型的物品用单调队列解即可。但如果不考虑超过NOIP范围的算法的话,用P03中将每个这类物品分成O(log n[i])个01背包的物品的方法也已经很优了。
    当然,更清晰的写法是调用我们前面给出的三个相关过程

    1 for i=1..N
    2     if 第i件物品是01背包
    3         ZeroOnePack(c[i],w[i])
    4     else if 第i件物品是完全背包
    5         CompletePack(c[i],w[i])
    6     else if 第i件物品是多重背包
    7         MultiplePack(c[i],w[i],n[i])

    二维费用的背包问题:对于每件物品,具有两种不同的费用;选择这件物品必须同时付出这两种代价;对于每种代价都有一个可付出的最大值(背包容量)。问怎样选择物品可以得到最大的价值。
    设这两种代价分别为代价1和代价2,第i件物品所需的两种代价分别为a[i]和b[i]。两种代价可付出的最大值(两种背包容量)分别为V和U。物品的价值为w[i]。
    分析:费用加了一维,只需状态也加一维即可。
    f[i][v][u]表示前i件物品付出两种代价分别为v和u时可获得的最大价值
    状态转移方程就是:f[i][v][u]=max{ f[i-1][v][u], f[i-1][v-a[i]][u-b[i]]+w[i] };  (01)

               f[i][v][u]=max{ f[i-1][v][u], f[i][v-a[i]][u-b[i]]+w[i] };   (完全)
    求解:可以只使用二维的数组——当每件物品只可以取一次时变量v和u采用逆序的循环,当物品有如完全背包问题时采用顺序的循环。当物品有如多重背包问题时拆分物品

    eg.hdu 2159

    dp[i][j]——i:忍耐度,j:杀怪数,最大经验值;

     1 /*
     2 Problem: hdu2159
     3 Tips   : 二维费用完全背包 Dyadic_Complete_Pack
     4 Date   : 2015.8.8
     5 */
     6 #include <cstdio>
     7 #include <iostream>
     8 #include <sstream>
     9 #include <cmath>
    10 #include <cstring>
    11 #include <cstdlib>
    12 #include <string>
    13 #include <vector>
    14 #include <map>
    15 #include <set>
    16 #include <queue>
    17 #include <stack>
    18 #include <algorithm>
    19 using namespace std;
    20 #define ll long long
    21 #define _cle(m, a) memset(m, a, sizeof(m))
    22 #define repu(i, a, b) for(int i = a; i < b; i++)
    23 #define repd(i, a, b) for(int i = b; i >= a; i--)
    24 #define sfi(n) scanf("%d", &n)
    25 #define sfl(n) scanf("%I64d", &n)
    26 #define pfi(n) printf("%d
    ", n)
    27 #define pfl(n) printf("%I64d
    ", n)
    28 const int inf = 0x3f3f3f3f;
    29 const int maxn = 105;
    30 int n, m, k, s;
    31 int dp[maxn][maxn]; //dp[i][j]i忍耐度,j杀怪数,经验值
    32 
    33 void Dyadic_Complete_Pack(int a, int b)
    34 {
    35     for(int i = b; i <= m; i++)
    36         for(int j = 1; j <= s; j++)
    37             dp[i][j] = max(dp[i][j], dp[i-b][j-1]+a);
    38 }
    39 
    40 int main()
    41 {
    42     while(~scanf("%d%d%d%d", &n, &m, &k, &s))
    43     {
    44         memset(dp, 0, sizeof(dp));
    45         for(int i = 1; i <= k; i++)
    46         {
    47             int a, b;
    48             scanf("%d%d", &a, &b);
    49             Dyadic_Complete_Pack(a, b);
    50         }
    51         if(dp[m][s] < n)
    52             printf("-1
    ");
    53         else
    54         {
    55             int ans = 0;
    56             for( ; ans <= m; ans++)
    57                 if(dp[ans][s] >= n)
    58                     break;
    59             printf("%d
    ", m-ans);
    60         }
    61     }
    62     return 0;
    63 }
    hdu_2159

    更多拓展,日后边做边积累~

  • 相关阅读:
    Jquery 表单批量验证
    学习总结 本科学习生涯
    学习总结 大学英语四六级
    学习总结 普通话等级考试
    学习总结 NCRE二级和三级
    oracle程序块
    正则表达式
    redis常用命令
    FaaS 基于多租户技术 SaaS平台设计
    量化交易之网格情缘
  • 原文地址:https://www.cnblogs.com/LLGemini/p/4713801.html
Copyright © 2011-2022 走看看