zoukankan      html  css  js  c++  java
  • 浅谈最基础的三种背包问题

    蒟蒻谈一下自己对于背包问题的理解

    • 简介背包问题
    • 0/1背包及其优化
    • 完全背包及其优化
    • 多重背包及其优化
    • emmmmmmm,后面那几种背包不太会,就不讲了(滑稽)

    一、背包问题:

    引用百度百科一句话

    背包问题(Knapsack problem)是一种组合优化的NP完全问题。问题可以描述为:给定一组物品,每种物品都有自己的重量和价格,在限定的总重量内,我们如何选择,才能使得物品的总价格最高。问题的名称来源于如何选择最合适的物品放置于给定背包中。相似问题经常出现在商业、组合数学,计算复杂性理论、密码学和应用数学等领域中。也可以将背包问题描述为决定性问题,即在总重量不超过W的前提下,总价值是否能达到V?它是在1978年由Merkel和Hellman提出的。
    背包问题已经研究了一个多世纪,早期的作品可追溯到1897年 [1] 数学家托比亚斯·丹齐格(Tobias Dantzig,1884-1956)的早期作品 [2] ,并指的是包装你最有价值或有用的物品而不会超载你的行李的常见问题。

    (好吧其实写不写都是无所谓的,主要是懒得自己介绍了,这些东西知道就好了)
    其实所有背包问题都是很相似的,中心特点就是放各种东西
    1、能放几个?
    2、怎么放?

    二、0/1背包及其优化

    (1)、0/1背包基础做法:

    题目描述
    一个旅行者有一个最多能用 m 公斤的背包,现在有 n 件物品,它们的重量分别是 W1 ,W2 ,… , Wn ,它们的价值分别为 C1,C2 ,… ,Cn 。若每种物品只有一件求旅行者能获得最大总价值。
    输入格式
    第 1 行:两个整数,M(背包容量,M≤200)和 N(物品数量,N≤30)。
    第 2..N+1 行:每行二个整数 Wi,Ci,表示每个物品的重量和价值。
    输出格式
    仅一行,一个数,表示最大总价值。
    样例数据 1
    输入
    10 4
    2 1
    3 3
    4 5
    7 9
    输出
    12

    基础的0/1背包的板子题,主要特点就是只能放一个。
    对于每一件物品来说,只有两个选择:放?还是不放?
    不放的话总价值就等于原价值,而放了的话当前背包的价值就等于当前背包已占有容量减去该物品占有容量的价值加上该物品的价值,分析过程
    如果一个个物品从左往右递推的话
    我们可以用数组dp [ i ] [ j ]表示推到第 i 个物品时,占据的空间为 j 时的最大价值
    那么我们就可以列出方程式

    f[i][j]=max(f[i1][j],f[i1][jw[i]]+c[i])

    贴出核心代码

    for(int i=1;i<=n;i++)
    {
            for(int j=1;j<=m;j++)
            {
                if(w[i]<=j) 
                dp[i][j]=max(dp[i-1][j-w[i]]+c[i],dp[i-1][j]);
                else dp[i][j]=dp[i-1][j];
            }
    }
    

    可能还有个不算问题的nc问题(只是备注一下而已,Rbl的问题)
    枚举空间时为什么是从1到m依次加1
    其实如果有那个能耐,可以直接把1~m之间的所有能凑出来的总价值罗列出来从小到大排个序,但反而得不偿失,谁要是真的无聊可以这样做。
    (2)、0/1背包的优化:
    时间复杂度 O ( m n ) 已经到极限了,基本上是无法优化的,当然我不知道上面那个算不算也许可能应该大概能优化时间,我们主要考虑优化空间S(m n ) 成S( m );
    重点理解!后面完全背包也有很大用!
    这样写

    for(int i=1;i<=n;i++)
    {
        for(int j=m;j>=1;j--)
        {
            if(w[i]<=j)
            dp[j]=max(dp[j],dp[j-w[i]]+c[i]);
        }
    }
    cout<<dp[m]<<endl;
    

    那么为什么能这样做呢?
    首先要注意到内层循环是从m~1递减的,理解了这一点,也就理解了这个优化
    看很多解释的模模糊糊,似懂非懂的的
    其实很简单考虑在一个矩阵上

    dp 1 2 3 …… j-1 j
    i xxxxx
    i-1 xxxx xxxxx xxxxx xxxx xxxxx xxxxx

    对于 j -1而言,更新前 dp [ j -1 ]存储的是在i-1时j-1的值,而此刻若它被更新,则是用dp [ j - w [ i ] ]的值,而此刻存储的是 j - 1时dp [ j - w [ i ] ]的值
    所以说

    if(w[i]<=j)
    dp[j]=max(dp[j],dp[j-w[i]]+c[i])
    

    等价于

    if(w[i]<=j) 
    dp[i][j]=max(dp[i-1][j-w[i]]+c[i],dp[i-1][j]);
    else dp[i][j]=dp[i-1][j] ;
    

    因为此刻dp [ j ] 本身就存储的dp [ j - 1 ] 的值,所以不需要 else 那一句
    而如果我们从前往后推的话,方程式相当于

    if(w[i]<=j) 
    dp[i][j]=max(dp[i][j-w[i]]+c[i],dp[i-1][j]);
    else dp[i][j]=dp[i-1][j] ;
    

    则是错误的

    感性理解一下

    附上代码

    #include<algorithm>
    #include<iostream>
    using namespace std;
    int a[205],s[205],n,m,dp[205];
    int main(){
    cin>>m>>n;
    for(int i=1;i<=n;i++)
    cin>>a[i]>>s[i];
    for(int i=1;i<=n;i++)
    {
        for(int v=1;v<=m;v++)
        {
            if(v>=a[i])dp[v]=max(dp[v],dp[v-a[i]]+s[i]);
        }
    }
    cout<<dp[m]<<endl;
    return 0;
    

    }

    三、完全背包

    题目描述
    设有 n 种物品,每种物品有一个重量及一个价值。但每种物品的数量是无限的,同时有一个背包,最大载重量为 M ,今从 n 种物品中选取若干件(同一种物品可以多次选取),使其重量的和小于等于 M ,而价值的和为最大。
    输入格式
    第 1 行:两个整数,M(背包容量,M<=200)和 N(物品数量,N<=200)。
    第 2..N+1 行:每行二个整数 Wi,Ci,表示每个物品的重量和价值。
    输出格式
    仅一行,一个数,表示最大总价值。
    样例数据 1
    输入
    12 4
    2 1
    3 3
    4 5
    7 9
    输出
    15

    考虑和0/1背包相似的做法,只是对于每个物品枚举一下取的数量就可以了;
    核心方程

    f[i][v]=max(f[i1][vk[i]]+kw[i]|0<=kc[i]<=v)

    代码什么的就不需要了,能想懂就能写得出来
    复杂度优化
    复杂度明显偏高,但是我不会证明复杂度。。。。。
    应该估计也许大概有接近
    考虑优化!
    和0/1背包类似的优化
    这也是为什么要好好理解0/1背包的优化的原因

    for(int i=1;i<=n;i++)
    {
        for(int j=1;j<=m;j++)
        {
        if(w[i]<=j)
        dp[j]=max(dp[j],dp[j-w[i]]+c[i]);
        }
    }
    cout<<dp[m]<<endl;
    

    注意,这里内层循环是1~n
    为什么?
    引用自崔添翼大佬的背包九讲

    首先想想为什么0/1背包中中要按照 j = m ~ 1的逆序来循环。这是因为要保证第i次循环中的状态 f [ i ] [ j ] 是由状态 f [ i - 1 ] [ j - w [ i ] ] 递推而来。换句话说,这正是为了保证每件物品只选一次,保证在考虑“选入第i件物品”这件策略时,依据的是一个绝无已经选入第i件物品的子结果 f [ i - 1 ] [ j - w [ i ] ] 。而现在完全背包的特点恰是每种物品可选无限件,所以在考虑“加选一件第i种物品”这种策略时,却正需要一个可能已选入第i种物品的子结果 f [ i ] [ j - w [ i ] ],所以就可以并且必须采用 j = 1~1的顺序循环。这就是这个简单的程序为何成立的道理。

    多重背包

    题目

    有N种物品和一个容量为V的背包。第i种物品最多有n[i]件可用,每件费用是c[i],价值是w[i]。求解将哪些物品装入背包可使这些物品的费用总和不超过背包容量,且价值总和最大。(没找到板子,只能找个大概的title)

    考虑和完全背包差不多的做法
    方程:

    f[i][v]=max(f[i1][vkc[i]]+kw[i]|0<=k<=n[i])

    其实差不多
    或者我们可以转变成0/1背包
    将可以取 n [ i ] 次的物品变成 n [ i ] 个只能取一次的物品
    (但其实两种写法的复杂度是一样的,都是O(V * Σ n [ i ]),不过第二种要好写些,毕竟只要两个循环)
    考虑真正意义上的优化(滑稽)
    将每个物品分成若干件物品,其中每件物品有一个系数,这件物品的费用和价值均是原来的费用和价值乘以这个系数。使这些系数分别为1,2,4,…,2^(k-1),n[i]-2^k+1,且k是满足n[i]-2^k+1>0的最大整数。
    可以证明1~ n [ i ] 中任意一个数都可以被表示出来
    仍然转换成了一个0/1背包问题
    但复杂度从O(V * Σ n [ i ])被优化成了O(V * Σ log n [ i ] )
    代码什么的就不贴了吧

    其实
    多重背包还有个O ( m n )的做法,利用单调队列优化,当然我也不会,可以参考楼教主的《男人八题》

    参考资料《背包九讲》

  • 相关阅读:
    更改Ubuntu默认python版本的方法
    hdu 5656 CA Loves GCD(dp)
    hdu 5655 CA Loves Stick
    hdu 5650 so easy (异或)
    2016.3.28
    Android 之 ExpandableListView 的使用
    Android之字符串的拆分-split
    Android之SAX解析XML
    hdu 5642 King's Order(数位dp)
    hdu 5641 King's Phone(暴力模拟题)
  • 原文地址:https://www.cnblogs.com/forever-/p/9736100.html
Copyright © 2011-2022 走看看