(转)完全背包

文章作者:Yx.Ac   文章来源:勇幸|Thinking (http://www.ahathinking.com)   转载请注明,谢谢合作。

---

前面回顾了01背包,在此基础上本节回顾完全背包的几种实现形式,主要有以下几方面内容:

==完全背包问题定义 & 基本实现

==完全背包二进制拆分思想

==完全背包使用滚动数组(略)

==完全背包中的逆向思维

==完全背包使用一维数组

========================================

完全背包问题定义 & 基本实现

问题:有个容量为V大小的背包,有很多不同重量weight[i](i=1..n)不同价值value[i](i=1..n)的货物,每种物品有无限件可用,想计算一下最多能放多少价值的货物。

与01背包不同的是,完全背包每件物体可以放入无限件(只要能放的下),故对于每件物品i,相当于拆分成了v/c[i]件相同的物品,拆分之后物品i就不是放入或不放入的两种情况了,而是放入0件、放入1件、放入2件…等情况了,对于该件物品i,最大价值取放入k件的最大值,故状态转移方程为:


1

f(i,v) = max{ f(i-1,v-k*c[i]) + k*w[i] | 0<=k<=v/c[i] }

各状态的意义不再赘述,上代码,关于复杂度以及每种物品的状态数见代码注释:


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

#include <iostream>

using namespace std;

/* 完全背包 版本1

 * Time Complexity  大于O(N*V)

 * Space Complexity O(N*V)

 * 设 V <= 200 N <= 10

 * 状态转移方程:f(i,v) = max{ f(i-1,v-k*c[i]) + k*w[i] | 0<=k<=v/c[i]  }

 * 每件物品有v/c[i]种状态

 */

int maxV[11][201];    /* 记录子问题最优解,物品可重复 */

int weight[11];

int value[11];

int V, N;

void main()

{

    int i, j, k;

    scanf("%d %d",&V, &N);

    for(i = 0; i < N; ++i)

    {

        scanf("%d %d",&weight[i],&value[i]);

    }

    for(i = 0; i < N; ++i)

    {

        for(j = 0; j <= V; ++j)

        {

            if(i > 0)

            {

                maxV[i][j] = maxV[i-1][j];

                if(j/weight[i] >= 1)

                {

                    int max_tmp = 0;

                    for(k = 1; k <= j/weight[i]; ++k)

                    {

                        if(maxV[i-1][j-k*weight[i]] + k*value[i] > max_tmp)

                        {

                            max_tmp = maxV[i-1][j-k*weight[i]] + k*value[i];

                        }

                    }

                    maxV[i][j] = maxV[i][j] > max_tmp ? maxV[i][j] : max_tmp;

                }

            }else

            {

                if(j/weight[0] >= 1)

                {

                    maxV[0][j] = j/weight[0] * value[0];

                }

            }

        }

    }

    printf("%d",maxV[N-1][V]);

}

========================================

完全背包二进制拆分思想

这种实现方式是对完全背包的基本实现做了一个优化,叫“二进制拆分”。所谓“拆分物体”就是将一种无限件物品拆分成有效的几件物品,拆分物体的目的是通过减少物品个数来降低复杂度。

在完全背包中,每种物品i对于容量v来讲实际上相当于有v/c[i]件,故在上述的基本实现中,k就要循环测试v/c[i]次。

这里的拆分是利用了一种二进制思想:即任何数字都可以表示成若干个2^k数字的和,例如7可以用1+2+2^2表示;这很好理解,因为任何正数都可以转成二进制,二进制就是若干个“1”(2的幂数)之和。

所以不管最优策略选择几件物品i,我们都可以将物品i拆成费用为c[i]*2^k,价值为w[i]*2^k的若干件物品。这样物品的状态数就降为了log(v/c[i]),是很大的改进。

在代码实现上,与基本实现的差别很小,区别如下


1

2

3

4

5

6

7

8

9

10

11

12

13

14

/* 完全背包 版本2

 * Time Complexity  大于O(N*V)

 * Space Complexity O(N*V)

 * 设 V <= 200 N <= 10

 * 状态转移方程:f(i,v) = max{ f(i-1,v-2^k*c[i]) + 2^k*w[i] | 0<=k<=log v/c[i]  }

 * 每件物品降低为 log(v/c[i]) 种状态

 */

for(k = 1; k <= j/weight[i]; k <<= 1)

{

     if(maxV[i-1][j-k*weight[i]] + k*value[i] > max_tmp)

     {

          max_tmp = maxV[i-1][j-k*weight[i]] + k*value[i];

     }

}

对于使用滚动数组的实现,这里就不写了,跟01背包是一样的。

========================================

完全背包中的逆向思维

我们知道,在01背包和完全背包的实现中,都是针对每种物品进行讨论,即外循环都是for i=0…n,然后每种物品对于容量v的变化而求得最大价值;

在完全背包中,由于物品的件数无限,所以我们可以倒过来想,我们针对每个容量讨论,外循环为容量,对于每个容量j,我们求j对于所有物品能装载的最大价值,这样一来我们就能将时间复杂度降为O(N*V)了。代码如下:


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

#include <iostream>

using namespace std;

/* 完全背包 版本3

 * Time Complexity  O(N*V)

 * Space Complexity O(N*V)

 * 设 V <= 200 N <= 10

 */

int maxValue[201][11]; /* 记录子问题的各状态 */

int weight[11];

int value[11];

int maxV[201]; /* 记录子问题的最优解 */

int V, N;

void main()

{

    int i, j;

    scanf("%d %d",&V, &N);

    for(i = 0; i < N; ++i)

    {

        scanf("%d %d",&weight[i],&value[i]);

    }

    for(i = 1; i <= V; ++i)

    {

        int i_maxV = 0;        /* 记录子问题i的最优解 */

        /* 每个容量求面对所有物体能装载的最大价值 */

        for(j = 0; j < N; ++j)

        {

            if(i >= weight[j])

            {

                int tmp = maxV[i-weight[j]] + value[j];

                maxValue[i][j] = maxV[i-1] > tmp ? maxV[i-1] : tmp;

            }else

            {

                maxValue[i][j] = maxV[i-1];

            }

            if(maxValue[i][j] > i_maxV)

            {

                i_maxV = maxValue[i][j];

            }

        }

        maxV[i] = i_maxV;

    }

    printf("%d",maxV[V]);

    /* 重定向输出结果到文件 */

    freopen("C:\\dp.txt","w",stdout);

    for(i = 0; i <= V; ++i)

    {

        for(j = 0; j < N; ++j)

        {

            printf("%d ",maxValue[i][j]);

        }

        printf("   %d\n",maxV[i]);

    }

}

同样,可以将状态转移矩阵重定向输出到文件进行对比,一看就明白了,这里就不贴图片了。

========================================

完全背包使用一维数组

对于01背包和完全背包,无论是空间复杂度还是时间复杂度,最优的方法还是使用一维数组进行实现。

基于01背包的分析,由于不必考虑物品的重复放入,故v的循环采用顺序即可。代码如下:


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

#include <iostream>

using namespace std;

/* 完全背包 版本4

 * Time Complexity  O(N*V)

 * Space Complexity O(V)

 * 设 V <= 200 N <= 10

 * 状态转移方程:v =0...V; f(v) = max{ f(v), f(v-c[i])+w[i] }

 */

int maxV[201];    /* 记录前i个物品中容量v时的最大价值, 物品可重复 */

int weight[11];

int value[11];

int V, N;

void main()

{

    int i, j;

    scanf("%d %d",&V, &N);

    for(i = 0; i < N; ++i)

    {

        scanf("%d %d",&weight[i],&value[i]);

    }

    for(i = 0; i < N; ++i)

    {

        for(j = weight[i]; j <= V; ++j)  /* j<weight[i]的对前面的状态不会有影响 */

        {

            int tmp = maxV[j-weight[i]]+value[i];

            maxV[j] = (maxV[j] > tmp) ? maxV[j] : tmp;

        }

    }

    printf("%d",maxV[V]);

}

========================================

PS:值得一提的是,在01背包和完全背包中,我们用到了两种思想,个人认为还是很有用的,其他地方也会用到很多,我们有必要在此留心:

  • 滚动数组压缩空间的思想
  • 二进制拆分的思想

记得有一回看到的面试题就用到了二进制拆分的思想,具体是啥忘了,以后碰到再说吧,就这样。

本文相关代码可以到这里下载。

(全文完)

参考资料:背包问题九讲 http://love-oriented.com/pack/

时间: 2025-01-06 00:27:39

(转)完全背包的相关文章

HDU 2189 悼念512汶川大地震遇难同胞——来生一起走(母函数或完全背包)

悼念512汶川大地震遇难同胞--来生一起走 Time Limit: 1000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)Total Submission(s): 3773    Accepted Submission(s): 1913 Problem Description 妈妈你别哭泪光照亮不了我们的路让我们自己慢慢的走 妈妈我会记住你和爸爸的模样记住我们的约定来生一起走 上面这首诗节选自一位诗人纪念遇难

UVA 562 Dividing coins --01背包的变形

01背包的变形. 先算出硬币面值的总和,然后此题变成求背包容量为V=sum/2时,能装的最多的硬币,然后将剩余的面值和它相减取一个绝对值就是最小的差值. 代码: #include <iostream> #include <cstdio> #include <cstring> #include <cmath> #include <algorithm> using namespace std; #define N 50007 int c[102],d

17-又见01背包

/*                                        又见01背包时间限制:1000 ms  |  内存限制:65535 KB难度:3 描述        有n个重量和价值分别为wi 和 vi 的 物品,从这些物品中选择总重量不超过 W     的物品,求所有挑选方案中物品价值总和的最大值.    1 <= n <=100    1 <= wi <= 10^7    1 <= vi <= 100    1 <= W <= 10^

Leetcode 494 Target Sum 动态规划 背包+滚动数据

这是一道水题,作为没有货的水货楼主如是说. 题意:已知一个数组nums {a1,a2,a3,.....,an}(其中0<ai <=1000(1<=k<=n, n<=20))和一个数S c1a1c2a2c3a3......cnan = S, 其中ci(1<=i<=n)可以在加号和减号之中任选. 求有多少种{c1,c2,c3,...,cn}的排列能使上述等式成立. 例如: 输入:nums is [1, 1, 1, 1, 1], S is 3. 输出 : 5符合要求5种

HDU - 2602 Bone Collector(01背包讲解)

题意:01背包:有N件物品和一个容量为V的背包.每种物品均只有一件.第i件物品的费用是volume[i],价值是value[i],求解将哪些物品装入背包可使价值总和最大. 分析: 1.构造二维数组:dp[i][j]---前i件物品放入一个容量为j的背包可以获得的最大价值. dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - volume[i]] + value[i]);---(a) (1)dp[i - 1][j]---不放第i件物品,因此前i件物品放入一个容量为

UVa 12563 劲歌金曲(0-1背包)

https://cn.vjudge.net/problem/UVA-12563 题意:求在给定时间内,最多能唱多少歌曲,在最多歌曲的情况下,使唱的时间最长. 思路:很明显背包容量为t-1,因为至少得留下1秒钟来放<劲歌金曲>.题目要求的首先唱的歌要多,其次才是要时间长. 这里需要用到一个技巧:对决策进行一定的限定!在计算某个时间最多唱的歌曲时,必须是该时间内恰好唱完这些歌,时间多了不行. 所以在下面的代码中,首先将d数组都声明为了-1,如果不是在该时间内正好唱完歌,那么d[j - a[i]]

(背包dp)UVA - 562 Dividing coins

题意:有n个硬币,每个硬币有个价值,两个人分配硬币,要求最公平分配时候两人拿到的钱的差. 分析:很明显,两人拿到的钱的差越小越公平. 一开始想,一定是一人一半最公平,所以直接把总和sum/2,对着half跑01背包,但是WA了,修改后分别讨论奇偶,额外进行一次sum-half的01背包,也WA,仔细想想觉得有些漏洞. 所以,这题其实可以干脆直接跑sum的背包,不断更新ans=min(ans,sum-dp[j]*2)就行了.如果ans==inf,表示不能分,也就是1个,这时输出0. 代码: 1 #

01背包

这里就只放自己刷的题目了,毕竟是弱弱哒 HDU2546:饭卡 1 #include <algorithm> 2 #include <cstdio> 3 4 using namespace std; 5 6 int main() 7 { 8 int n,m; 9 while (~scanf("%d", &n), n) 10 { 11 int f[2013] = {0}, menu[2013] = {0}; 12 for (int i = 1; i <

[hdu5445 Food Problem]多重背包

题意:一堆食物,有价值.空间.数量三种属性,一些卡车,有空间,价格,数量三种属性.求最少的钱(不超过50000)买卡车装下价值大于等于给定价值的食物,食物可以拆开来放. 思路:这题的关键是给定的条件:食物可以拆开来放.这个条件使得卡车和食物可以分开考虑,然后通过空间这个属性联系在一起.做两遍多重背包即可. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35

HDU 1011 Starship Troopers(树形dp+背包)

Starship Troopers Time Limit: 10000/5000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others) Total Submission(s): 13109    Accepted Submission(s): 3562 Problem Description You, the leader of Starship Troopers, are sent to destroy a base of