背包

01背包

 

这是最简单的一种背包,因为对于每一件物品都只有放和不放两种情况,故叫01背包。
所以状态转换方程
dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - c[i]] + v[i]);
dp[i][j]指有i个物品,在j个空间中能存放的最大价值
dp[i - 1][j]指第i个物品不放进背包,即和i- 1的情况相同
dp[i - 1][j - c[i]] + v[i]指第i个物品放进背包,那么就是j个空间中减去第i个物品所占的空间c[i],这种情况下在再加上它的价值v[i]

例题:
描述
辰辰是个很有潜能、天资聪颖的孩子,他的梦想是称为世界上最伟大的医师。为此,他想拜附近最有威望的医师为师。医师为了判断他的资质,给他出了一个难题。医师把他带到个到处都是草药的山洞里对他说:“孩子,这个山洞里有一些不同的草药,采每一株都需要一些时间,每一株也有它自身的价值。我会给你一段时间,在这段时间里,你可以采到一些草药。如果你是一个聪明的孩子,你应该可以让采到的草药的总价值最大。”

如果你是辰辰,你能完成这个任务吗?
输入
输入的第一行有两个整数T(1 <= T <= 1000)和M(1 <= M <= 100),T代表总共能够用来采药的时间,M代表山洞里的草药的数目。接下来的M行每行包括两个在1到100之间(包括1和100)的的整数,分别表示采摘某株草药的时间和这株草药的价值。
输出
输出只包括一行,这一行只包含一个整数,表示在规定的时间内,可以采到的草药的最大总价值。
样例输入
70 3
71 100
69 1
1 2
样例输出
3

代码

 1 #include<cstdio>
 2 #include<iostream>
 3 using namespace std;
 4 int dp[1005][1005], t[1005], v[1005];
 5 int T, M;
 6 int main()
 7 {
 8     scanf("%d%d", &T, &M);
 9     for(int i = 1; i <= M; ++i)
10         scanf("%d%d", &t[i], &v[i]);
11     for(int i = 0; i <= T; ++i) dp[0][i] = 0;   //初始化
12     for(int i = 1; i <= M; ++i)
13     {
14         dp[i][0] = 0;       //初始化
15         for(int j = T; j >= 0; --j)
16         {
17             dp[i][j] = dp[i - 1][j];
18             if(j - t[i] >= 0)   //如果不判断,数组下标会出负数
19                 dp[i][j] = max(dp[i][j], dp[i - 1][j - t[i]] + v[i]);
20         }
21     }
22     printf("%d\n", dp[M][T]);
23     return 0;
24 }


以上方法的时间和空间复杂度均为O(N*V),其中时间复杂度基本已经不能再优化了,但空间复杂度却可以优化到O(V),即改成一维数组
#include<cstdio>
#include<iostream>
using namespace std;
int dp[1005], t[1005], v[1005];
int T, M;
int main()
{
    scanf("%d%d", &T, &M);
    for(int i = 1; i <= M; ++i)
        scanf("%d%d", &t[i], &v[i]);
    for(int i = 1; i <= M; ++i)
        for(int j = T; j >= t[i]; j--)
                dp[j] = max(dp[j], dp[j - t[i]] + v[i]);
   printf("%d\n",dp[T]);
    return 0;
}

其中内层循环最好要从后往前,因为dp[j]一定是从前面更新来的。若是正着来,那么一个物品就可能被放进去多次。

 

多重背包


多重背包就是每一件物品都有多个(但是并不是无限的),求最大价值。

思路:转换为01背包。如:有容量为3,价值为5的物品12个,那么就可看成有12个物品。但时间复杂度会相当的大。

改进:
先看一道题:一个数n=63,将n分成6个数之和,使这6个数可以组合成从0到n之间任意的一个数,问这6个数是多少。
分析:我们发现,2^6-1=63,即63的二进制为111111,所以6个数分别为1,2,4,8,16,32。根据二进制运算,这几个数可组成0到111111之间任意一个数。
那么若n=60怎么办呢?
其实就是把最后一个数32-3。

所以对于多重背包问题,相同种类的物品就可进行合并:个数不断减2^n(n++),直到不够减为止,则剩下的就是最后一个数。
如:有容量为3,价值为5的物品12个。因为12=1+2+4+5,所以可转换为4个容量和价值分别为(3,5),(6,10),(12,20),(15,25)的物品。

代码

 1 #include<cstdio>
 2 #include<iostream>
 3 using namespace std;
 4 const int maxn = 10005;
 5 int dp[maxn], c[maxn], v[maxn]; //c为容量,v为价值
 6 int m, n, q = 0; //m为背包体积,n为物品种类, q记录合并后的物品数 
 7 void hebin(int C, int V,int GS)
 8 {
 9     int x = 1;
10     while(GS - x > 0)
11     {
12         c[++q] = x * C;
13         v[q] = x * V;
14         GS -= x;
15         x *= 2;
16     }
17     c[++q] = GS * C;
18     v[q] = GS * V;
19     return;
20 }
21 int main()
22 {
23     scanf("%d%d", &m ,&n); 
24     for(int i = 1; i <= n; ++i)
25     {
26         int cc, vv, gs;    //gs为一种物品的个数
27         scanf("%d%d%d", &cc, &vv, &gs);
28         hebin(cc, vv, gs); 
29     }
30     for(int i = 1; i <= q; ++i)
31     {
32         for(int j = m; j >= 0; --j)
33         {
34             if(j - c[i] >= 0)
35                 dp[j] = max(dp[j], dp[j - c[i]] + v[i]);
36         }
37     }
38     printf("%d\n", dp[m]);
39     return 0;
40 }

如此就将第i种物品分成了O(logn[i])种物品,将原问题转化为了复杂度为O(V*Σlog n[i])的01背包问题,是很大的改进

 

完全背包


其实就是每个物品有无限个,求最大价值。

思路:转化为多重背包,进而01背包。
可用所给的背包最大容量来确定每个物品的最大数量。
如:背包容量为10,有一个物品所占容量为3,那么它最多有10/3=3个。
这样就转化成了多重背包。
代码

 1 #include<cstdio>
 2 #include<iostream>
 3 using namespace std;
 4 const int maxn = 10005;
 5 int dp[maxn], c[maxn], v[maxn];
 6 int m, n, q = 0;
 7 void hebin(int C, int V, int M) //就这和多重背包不一样,M是总容量
 8 {
 9     int x = 1;
10     while(M - x * C >= 0)
11     {
12         c[++q] = x * C;
13         v[q] = x * V;
14         M -= x * C;
15         x *= 2;
16     }
17     if(M >= C)  //处理余下的同类物品
18     {
19         c[++q] = M / C * C;
20         v[q] = M / C * V;
21     }
22     return;
23 }
24 int main()
25 {
26     scanf("%d%d", &m, &n);
27     for(int i = 1; i <= n; ++i)
28     {
29         int cc, vv;
30         scanf("%d%d", &cc, &vv);
31         hebin(cc, vv, m); 
32     }
33     for(int i = 1; i <= q; ++i)
34         printf("c = %d v = %d\n", c[i], v[i]);
35     for(int i = 1; i <= q; ++i)
36     {
37         for(int j = m; j >= 0; --j)
38         {
39             if(j - c[i] >= 0)
40                 dp[j] = max(dp[j], dp[j - c[i]] + v[i]);
41         }
42     }
43     printf("%d\n", dp[m]);
44     return 0;
45 }

 

其实基本思路并不是这个。
不过别担心,这种解法比基本思路要快。但是为了完整性,我还是讲讲基本思路吧。
先上段伪代码

1 for(i...n)
2     for(j...m)        //m是背包容量
3     {                //c[]是物品容量,v[]是物品价值 
4         if(j - c[i] > 0)     
5             dp[j] = max(dp[j], dp[j - c[i]] + v[i])
6     }
7 printf("%d\n", dp[m]); 

觉得熟悉?
没错,它和01背包就差个内层循环,一个正着,一个倒着。那为什么这么一改,就从01背包转化为完全背包了呢?
首先我们要清楚的是,因为dp[j] = dp[j] 或 dp[j - c[i]] + v[i] ,所以每一次dp,要么保持不变,要么用前面的更新后面的。如果从0到m循环,也就可能导致在dp前i个物品时第i个物品被放进去了多次,那就不满足每一个物品只有1个,即01背包了。相反的,这不就是完全背包的解法吗?

水一段代码

#include<cstdio>
#include<iostream>
using namespace std;
int dp[1005], c[1005], v[1005];
int m, n;
int main()
{
    scanf("%d%d", &m, &n);
    for(int i = 1; i <= n; ++i)
        scanf("%d%d", &c[i], &v[i]);
    for(int i = 1; i <= n; ++i) //是的,就这改了
    {
        for(int j = 0; j <= m ; ++j)
        {
            if(j - c[i] >= 0)
                dp[j] = max(dp[j], dp[j - c[i]] + v[i]);
        }
    }
    printf("%d\n",dp[m]);
    return 0;
}

 

分组背包


有N件物品和一个容量为V的背包。第i件物品的费用是c[i],价值是w[i]。这些物品被划分为若干组,每组中的物品互相冲突,最多选一件。

思路:对于每一组,只有选其中一件,或什么都不选两种选择。
不选: dp[v] = dp[v]
选一组中的第i个:dp[v] = dp[v - c[i]] + w[i]
将两者比较,取最大
(v指当前容量为v的情况下)

伪代码:

1 for 所有的组k
2     for v=V..0
3         for 所有的i属于组k
4             f[v]=max{f[v],f[v-c[i]]+w[i]}

 

例题:P1757 通天之分组背包
题目描述

自01背包问世之后,小A对此深感兴趣。一天,小A去远游,却发现他的背包不同于01背包,他的物品大致可分为k组,每组中的物品相互冲突,现在,他想知道最大的利用价值是多少。

输入输出格式

输入格式:
两个数m,n,表示一共有n件物品,总重量为m

接下来n行,每行3个数ai,bi,ci,表示物品的重量,利用价值,所属组数

输出格式:
一个数,最大的利用价值
上代码

 1 #include<cstdio>
 2 #include<iostream>
 3 #include<cmath>
 4 #include<algorithm>
 5 using namespace std;
 6 const int maxn = 1005;
 7 int m, n, maxk = -1;        //m为背包容量,n为物品数
 8 int dp[maxn], c[105][maxn], v[105][maxn], zu[105];
 9 int main()                  //zu[]记录每一组中有多少个物品
10 {
11     scanf("%d%d", &m, &n);
12     for(int i = 1; i <= n; ++i)
13     {
14         int ci, vi, k;
15         scanf("%d%d%d", &ci, &vi, &k);
16         c[k][++zu[k]] = ci; v[k][zu[k]] = vi;
17         if(k > maxk) maxk = k;  //记录最大组数
18     }
19     for(int i = 1; i <= maxk; ++i)
20         for(int j = m; j >= 0; --j)
21             for(int q = 1; q <= zu[i]; ++q)
22                 if(j - c[i][q] >= 0)
23                     dp[j] = max(dp[j], dp[j - c[i][q]] + v[i][q]);
24     printf("%d\n", dp[m]);
25     return 0;
26 }

 

posted @ 2017-12-26 20:07  mrclr  阅读(256)  评论(0编辑  收藏  举报