• 多重背包


    说在前面:
    这篇文章是转载的,因为自己感觉也描述的不是很好
    然后呢,如果想要了解多重背包,可以结合下面三篇博文:
    http://www.cnblogs.com/favourmeng/archive/2012/09/07/2675580.html  二进制描述的很好
    http://blog.csdn.net/passion_acmer/article/details/52212443   代码很清晰,是最好理解的一篇
    http://www.ahathinking.com/archives/109.html     本文,讲的很详细,但有个位运算符号错了,我已经改正了
     

    前面已经回顾了01背包完全背包,本节回顾多重背包的几种实现形式,主要有以下几方面内容:

    ==多重背包问题定义 & 基本实现

    ==多重背包二进制拆分实现

    ==防火防盗防健忘

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

    多重背包问题定义 & 基本实现

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

    对于多重背包的基本实现,与完全背包是基本一样的,不同就在于物品的个数上界不再是v/c[i]而是n[i]与v/c[i]中较小的那个。状态转移方程如下

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

    代码与完全背包的区别仅在内部循环上由

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

    变为

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

    当然,输入上的区别就不说了。

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

    二进制思想

    问题描述:

      假设有1000个苹果,现在要取n个苹果,如何取?正常的做法应该是将苹果一个一个拿出来,直到n个苹果被取出来。

      又假设有1000个苹果和10只箱子,如何快速的取出n个苹果呢?可以在每个箱子中放 2^i (i<=0<=n)个苹果,也就是 1、2、4、8、16、32、64、128、256、489(最后的余数),相当于把十进制的数用二进制来表示,取任意n个苹果时,只要推出几只箱子就可以了。

    多重背包二进制拆分实现:

    跟完全背包一样的道理,利用二进制的思想将n[i]件物品i拆分成若干件物品,目的是在0-n[i]中的任何数字都能用这若干件物品代换,另外,超过n[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的四件物品。分成的这几件物品的系数和为n[i],表明不可能取多于n[i]件的第i种物品。另外这种方法也能保证对于0..n[i]间的每一个整数,均可以用若干个系数的和表示。

    代码如下:测试用例见代码末的注释

    #include <iostream>
    using namespace std;
     
    /* 多重背包 二进制拆分
     * Time Complexity  大于O(N*V)
     * Space Complexity O(N*V)
     * 设 V <= 200 N <= 10 ,拆分后 物品总数 < 50
     * 每件物品有 log n[i]种状态
     */
     
    int maxV[201];
    int weight[50]; /* 记录拆分后物体重量 */
    int value[50];  /* 记录拆分后物体价值 */
    int V, N;
     
    void main()
    {
        int i, j;
        scanf("%d %d",&V, &N);
        int weig, val, num;
        int count = 0;
     
        for(i = 0; i < N; ++i)
        {
            scanf("%d %d %d",&weig,&val,&num);
     
            for(j = 1; j <= num; j <<= 1) // 二进制拆分 位运算
            {
                weight[count] = j * weig;
                value[count++] = j * val;
                num -= j;
            }
            if(num > 0)
            {
                weight[count] = num * weig;
                value[count++] = num * val;
            }
        }
        for(i = 0; i < count; ++i)  // 使用01背包
        {
            for(j = V; j >= weight[i]; --j)
            {
                int tmp = maxV[j-weight[i]] + value[i];
                maxV[j] = maxV[j] > tmp ? maxV[j] : tmp;
            }
        }
        printf("%d",maxV[V]);
    }
     
    /*
        【输入样例】
        4 20
        3     9     3
        5     9     1
        9     4     2
        8     1     3
        【输出样例】
        47
    */

    简单背包基础总结:

    回顾了3种简单背包后,有些思想慢慢体会,实践中,对于01背包和完全背包使用一维数组实现是最简便高效的,对于多重背包,最好就是输入时进行二进制拆分,然后使用01背包,这样比基本实现和在运算时再进行拆分要简捷的多。

    防火防盗防健忘:

    没事水一下:POJ  PKU

    01背包: 321136243628

    完全背包:125213842063

    多重背包:10141276174223923260(完全+多重)

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

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

    (全文完)

  • 相关阅读:
    MySQL数据库----数据类型
    MySQL数据库----安装
    I2c串行总线组成及其工作原理
    感慨
    液晶操作
    串口通信
    9.19AD和DA操作
    9.19键盘的应用
    9.17键盘的操作
    9.15学习笔记
  • 原文地址:https://www.cnblogs.com/William-xh/p/7344550.html
Copyright © 2020-2023  润新知