• 背包九讲(转载)


    转载

    背包问题

     
    复制代码
    //0 1背包
    #include<iostream>
    #include<stdio.h>
    #include<string.h>
    using namespace std;
    int main()
    {    
        int i,j,n,v,f[1100],w[1100],p[1100];
        scanf("%d",&t);
        while(t--)
        {
           memset(f,0,sizeof(f));
            scanf("%d%d",&n,&v);
            for(i=0;i<n;i++)
                scanf("%d",&p[i]);
            for(i=0;i<n;i++)
                scanf("%d",&w[i]);
            for(i=0;i<n;i++)
            {
                for(j=v;j>=w[i];j--)
                    f[j]=f[j]>(f[j-w[i]]+p[i])?f[j]:(f[j-w[i]]+p[i]);
            }
            printf("%d
    ",f[v]);
        }
        return 0;
    }
    
    
    
    
    
    //完全背包
    #include<stdio.h>
    #define max 999999
    int main()
    {
        int i,j,n,v,f[10010],w[10010],p[10010],t,a,b;
        scanf("%d",&t);
        while(t--)
        {
            scanf("%d%d%d",&a,&b,&n);
            v=b-a;
            for(i=1;i<=v;i++)
                f[i]=max;
            f[0]=0;
            for(i=0;i<n;i++)
                scanf("%d%d",&p[i],&w[i]);
            for(i=0;i<n;i++)
                for(j=w[i];j<=v;j++)
                    if(f[j]>f[j-w[i]]+p[i])
                        f[j]=f[j-w[i]]+p[i];
           if(f[v]==max)  printf("This is impossible.
    ");
            else
            printf("The minimum amount of money in the piggy-bank is %d.
    ",f[v]);
        }
        return 0;
    }
    复制代码

    01背包是指每个物品只能用一次
    完全背包指的是每个物品都能无限使用
    没什么技巧,DP类的明确了状态后就写起来快了,写多了程序自然也就有经验了,有经验就能快速的推出状态,总之就是多写题 

    程序的不同很简单
    完全背包
    for i:=1 to n do //枚举1-N的物品
    for j:=a[i] to m do //枚举1-M的能背出来的范围
         f[j]:=f[j] or f[j-a[i]];
    01背包
    for i:=1 to n do //枚举1-N的物品
    for j:=m downto a[i] do //枚举1-M的能背出来的范围
         f[j]:=f[j] or f[j-a[i]];
    注意,之前要先f[0]:=true;

    可以看出,区别只在第二个循环的正倒,F是一个布尔数组,F[i]表示i这个数字能够被组合出来
    为什么循环的正倒会导致这样的区别呢?
    我们举个例子:
    假设我们现在组合出了3,现在讨论的物品体积是1,那么,即a[i]=1, f[3]:=true;
    当正循环时
    当枚举到4的时候,f[4]=f[4] or f[4-1]=true,因为f[3]=true;
    当枚举到5的时候,f[5]=f[5] or f[5-1]=true,因为f[4]=true;
    显然,这个“1”会被无限的使用下去直到到达M的上限
    逆循环则相反

    复制的

    标注:下文n即为物品件数,c[i]表示第i件物品的耗费(体积),V为背包容量,a[i]表示第i件物品的价值 dp[]数组存放的即为最优解。

    一、01背包

    最简单的背包,每件物品选或者不选。

    1 for(int i = 1; i <= n; ++i){
    2  for(int j = V; j >= c[i]; --j){
    3   dp[j] = max(dp[j],dp[j-c[i]]+a[i]);
    4  }
    5 }

    二、完全背包

    每件物品可以选无限次

    1 for(int i = 1; i <= n; ++i){
    2  for(int j = c[i]; j <= V; ++j){
    3   dp[j] = max(dp[j],dp[j-c[i]] + a[i];
    4  }
    5 }

    注意:初始化方面的细节,如果要求恰好装满背包,那么在初始化时除了dp[0]为0其它dp[1..V]均设为-∞(求的是最大解,如果求的是最小解,则为∞)  如果并没有要求必须把背包装满,而是只希望价格尽量大,初始化时应该将f[0..V]全部设为0。

    三、多重背包

    每件物品可以选有限次  设每件物品的个数为count[i];

    O(V*Σlog count[i])写法(二进制优化)

    复制代码
     1 for(int i = 1; i <= n; ++i){
     2  int k;
     3  for(k = 1; k*2 < count[i] + 1; k *= 2){
     4   for(int j = V; j >= k*c[i] ; --j){
     5    dp[j] = max(dp[j],dp[j-k*c[i]] + k*a[i]);
     6   }
     7  }
     8  k = count[i] + 1 - k;         //k等于前面项数之和+1,(k-1)就是前面项数之和,1-k为其相反数
     9  for(int j = V; j >= k*c[i]; --j){
    10   dp[j] = max(dp[j],dp[j-k*c[i]] + k*a[i]);
    11  }
    12 
    13 }
    复制代码

    O(VN)的写法

    /*此种方法求最大值不确定可否,但判断是否能将1-V内的背包装满可以*/

    ----------------------------------------------------------------------

    复制代码
     1 int used[MAXN];
     2 for(int i = 1; i <= N; ++i){
     3  memset(used,0,sizeof(used));
     4  for(int j = c[i]; j <= V; ++j){
     5   if(used[j-c[i]] < count[i] && dp[j] < dp[j-c[i]] + a[i]){   
     6    dp[j] = dp[j-c[i]] + a[i];   
     7    used[j] = used[j-c[i]] + 1;
     8   }
     9  }
    10 }  //有待验证,估计不对,有兴趣的可以验证下
    复制代码

    ----------------------------------------------------------------------

    现在dp[i]用来表示容量为i的背包能否被所给物品恰好装满 上面的01背包和完全背包以及上面一种O(V*Σlog count[i])的写法也可以做这样的改变

    复制代码
     1 memset(dp,false,sizeof(dp));
     2 dp[0] = true;
     3 for(int i = 1; i <= N; ++i){
     4  memset(used,0,sizeof(used));
     5  for(int j = c[i]; j <= V; ++j){ //注意这里是顺序的(和完全背包相同),其实多重背包也可以理解成被限制了的完全背包
     6   if(!dp[j] && dp[j-c[i]] && used[j-c[i]] < count[i]){ //注意这里的!dp[j]不能少
     7    dp[j] = true; 
    8 used[j] = used[j-c[i]] + 1; 9 } 10 } 11 }
    复制代码

    四、混合背包

    以上三种背包的混合,有的物品只能取一次,有的物品能取无限次,有的物品能取有限次

    算法伪代码:

    for i=1..n

        if 第i件物品是01背包

            for j=V...c[i]

    dp[j]=max();   //同上面01背包代码 

        else if 第i件物品是完全背包

            for j=c[i]...V

    dp[j]=max();  //同上面完全背包

        else if 第i件物品是多重背包

            MultiplePack(c[i],a[i],count[i]) //同上多重背包

    五、二维费用的背包

    如问题:每件物品都有各自的体积v[i]和重量w[i],一个背包最多可以装的体积为V,最多可以装的重量为W,问最多能装的价值。即有两种属性限制的背包问题。再比如,一个一般的01背包问题,再加上个数限制,即最多拿K件物品,也可以理解为每件物品的个数耗费为1,背包再加一个容量为K的属性,也是二维费用背包。对于二维费用的背包,只需对dp数组加一维即可,01背包倒序,完全背包正序

    以01背包为例:

    for i=1...n

         for j=V..v[i];  //若为完全背包则写为for j=v[i]..V  for k=w[i]..W;

    for k=W..w[i];

    dp[j][k] = max(dp[j][k],dp[j-v[i]][k-w[i]] + a[i]);

    六、分组背包的问题

    将n件物品分为若干组,每组中只能选择一件物品,问可以得到的最大价值。

    for 所有的组k

        for v=V..0

            for 所有的i属于组k

                dp[v]=max{dp[v],dp[v-c[i]]+a[i]}

    七、有依赖的背包问题

    要购买一件物品,必须先购买另一件物品。这就是有依赖关系的背包问题 背包九讲里讲的很清楚了,我就不多说了。

    下面两个部分暂且放下。以后遇到具体的问题时再补充。

    八、泛化物品的背包

    九、背包问题问法的变化

    下面就说几个比较经典的题目:01背包 完全背包就不说了,混合背包也没必要说

    一、【POJ1014】【TOJ1034】【HDU1059】【ZOJ1149】Dividing——多重背包问题

     一般在几个OJ上同时出现并且长久不衰的题都是经典题目,比如这题,是一道关于多重背包的题目

    【题目大意】6种价值分别为1 2 3 4 5 6的石头,给出每种石头的数目,问能不能将这些石头分为价值相等的两堆。

    【解析】经典的多重背包问题,a[]数组即为1 2 3 4 5 6,count数组题目给出,怎么做呢?上面提到一点,设dp[i]表示容量为i的背包能不能被所给物体装满。如果dp[sum/2]==1,那么就说明可以分为两堆(sum是所有石头的总价值),直接套用上面的模板即可。

    复制代码
     1 #include <cstdio>
     2 #include <cstring>
     3 using namespace std;
     4 
     5 int a[10];
     6 int dp[120100];
     7 int main(){
     8     int cas = 0;
     9     while( ++ cas ){
    10         int sum = 0;
    11         for(int i = 1; i <= 6; ++i){
    12             scanf("%d",&a[i]);
    13             sum += a[i]*i;
    14         }
    15         if(sum == 0)break;
    16 
    17         memset(dp,0,sizeof(dp));
    18         printf("Collection #%d:
    ",cas);
    19 
    20         if(sum % 2 == 1){
    21             puts("Can't be divided.");
    22             puts("");
    23             continue;
    24         }
    25 
    26         dp[0] = 1;
    27         //O(NV)的方法
    28         int dpt[120000];
    29         for(int i = 1; i <= 6; ++i){
    30             memset(dpt,0,sizeof(dpt));
    31             for(int j = i; j <= sum/2; ++j){
    32                 if (!dp[j] && dp[j-i]&& dpt[j-i] < a[i]) {
    33                     dpt[j] = dpt[j-i] + 1;
    34                     dp[j] = 1;
    35                 }
    36             }
    37         }
    38         /*
    39        //二进制优化的方法
    40         
    41        for(int i = 1;i <= 6;++i){
    42             int k;
    43             for(k = 1;k*2 < a[i]+1;k *= 2)
    44             for(int v = sum/2;v >= k*i;--v)
    45                 if(dp[v-k*i])
    46                     dp[v]=true;
    47             k = a[i]+1-k;
    48             for(int v = sum/2;v > k*i;--v)
    49                 if([v-k*i])
    50                     dp[v] = true;
    51 
    52         }
    53        */
    54         if(dp[sum/2]){
    55             puts("Can be divided.");
    56         }
    57         else {puts("Can't be divided.");};
    58         puts("");
    59 
    60     }
    61     return 0;
    62 }
    复制代码

     二、【TOJ3540】Consumer有依赖关系的背包

    【题目大意】给若干组物品,每组物品都有一个箱子(箱子自身也有cost),然后就是物品的cost和value,要买某个物品必须也要买装这个物品的箱子,给一定钱数,问能获得的最大价值。

    【题目解析】可以参照背包九讲第六讲和第七讲里的内容解决该问题(相当对口,这题就是根据背包九讲来的)

    有一种比较简单的写法,两次背包即可。

    复制代码
     1 #include<iostream>
     2 using namespace std;
     3 const int MAXN = 100000;
     4 int main()
     5 {
     6     int n,total,boxcost,goodnum,cost,value,i,j,t;
     7     int dpbox[MAXN],dptotal[MAXN];
     8     while(scanf("%d%d",&n,&total) != EOF)
     9     {
    10         memset(dptotal,0,sizeof(dptotal));
    11         for(i = 0; i < n; i++)
    12         {
    13             scanf("%d%d",&boxcost,&goodnum);
    14             memcpy(dpbox,dptotal,sizeof(dptotal));
    15             for(j = 0; j < goodnum; j++)
    16             {
    17                 scanf("%d%d",&cost,&value);
    18                 for(t = total - boxcost; t >= cost; t--)
    19                 {
    20                     if(dpbox[t] < dpbox[t - cost] + value)
    21                     dpbox[t] = dpbox[t-cost] + value;
    22                 }
    23             }
    24             for(t = total; t >= boxcost; t--)
    25             if(dptotal[t] < dpbox[t-boxcost])
    26             dptotal[t] = dpbox[t-boxcost];
    27         }
    28         printf("%d
    ",dptotal[total]);
    29     }
    30     return 0;
    31 }
    复制代码

    三、【TOJ3645】Are You Busy

    这题源自多校联合赛,是一个比较难的背包综合题

    【题目大意】一个人有T分钟去做工作,共有n组工作,每一组有若干工作,每个工作消耗的时间和得到的快乐值已知,每一个组有一个标识位,标识位为0,则该组中至少应该选取一样工作完成,标识位为1,则该组中至多有一个工作被选择,标识位为2,无限制。

    【分析】对标识位为2的工作组,因为无限制条件,每个工作可做可不做,即用简单的01背包即可处理。对标识位为1的工作组,因为至多选择一个工作,所以本次操作只对dp数组选取最优值更新一次即可。对至少选择一个的工作组,还不是太理解,只是看别人代码写的,还请大家集思广益,有想法可以留言评论,以后理解透彻了以后会做补充。

    复制代码
     1 #include <cstdio>
     2 #include <algorithm>
     3 #include <cstring>
     4 #include <cmath>
     5 #include <queue>
     6 #include <vector>
     7 #include <map>
     8 
     9 using namespace std;
    10 
    11 const int maxt = 100 + 10;
    12 
    13 int dp[maxt];
    14 int td[maxt], n, m, t;
    15 
    16 void dp0() {   //至少取一个
    17     memcpy (td, dp, sizeof(td));
    18     memset (dp, -1, sizeof(dp));
    19     for (int i = 0, a, b; i < m; ++i) {
    20         scanf ("%d%d", &a, &b);
    21         for (int j = t; j >= a; --j) {//注意怎样保证至少取一个??
    22             if (dp[j-a] != -1) {
    23                 dp[j] = max(dp[j], dp[j-a] + b);
    24             }
    25             if (td[j-a] != -1) {
    26                 dp[j] = max(dp[j], td[j-a] + b);
    27             }
    28         }
    29     }
    30 }
    31 
    32 void dp1() {   //最多取一个,用上次得到的dp值更新最优值,只更新一次
    33     memcpy(td, dp, sizeof(td));
    34     for (int i = 0, a, b; i < m; ++i) {
    35         scanf ("%d%d", &a, &b);
    36         for (int j = t; j >= a; --j) {
    37             if (td[j-a] != -1) {     //用-1初始化,这种情况能够被装入的都是恰好被装入
    38                 dp[j] = max(dp[j], td[j-a] + b);
    39             }
    40         }
    41     }
    42 }
    43 
    44 void dp2() {  //无条件限制,01背包,更新多次
    45     for (int i = 0, a, b; i < m; ++i) {
    46         scanf ("%d%d", &a, &b);
    47         for (int j = t; j >= a; --j) {
    48             if (dp[j-a] != -1) {
    49                 dp[j] = max(dp[j], dp[j-a] + b);
    50             }
    51         }
    52     }
    53 }
    54 
    55 void init() {
    56     memset (dp, -1, sizeof(dp));
    57     dp[0] = 0;
    58     for (int i = 0, s; i < n; ++i) {
    59         scanf ("%d%d", &m, &s);
    60         if (s == 0) dp0();
    61         else if (s == 1) dp1();
    62         else if (s == 2) dp2();
    63     }
    64 }
    65 
    66 void solve() {
    67     int ans = -1;
    68     for (int i = 0; i <= t; ++i)
    69         ans = max(ans, dp[i]);
    70     printf ("%d
    ", ans);
    71 }
    72 
    73 int main() {
    74     //freopen("ar.txt","r",stdin);
    75     while (scanf ("%d%d", &n, &t) != EOF) {
    76         init();
    77         solve();
    78     }
    79     return 0;
    80 }
    复制代码
     
    分类: 背包
     
    绿色通道: 好文要顶 关注我 收藏该文与我联系 
    0
    0
     
    (请您对文章做出评价)
     
    « 上一篇:最短路径———迪杰斯特拉算法
    » 下一篇:二维背包问题
    posted @ 2012-08-22 16:24 萧凡客 阅读(135) 评论(0) 编辑 收藏
     
     
     
  • 相关阅读:
    05 css继承性
    04 选择器权重
    03 css三种引入的方式
    02 css实现举例
    01 css介绍
    05 dl-添加定义列表
    04 ol-热门点击排行榜
    02 h1 p hr br 特殊符号
    01html简介
    函数内置方法
  • 原文地址:https://www.cnblogs.com/xianbin7/p/4438790.html
Copyright © 2020-2023  润新知