动态规划——背包问题

背包问题是一类非常典型的动态规划问题,包括多种类型(01背包、完全背包、多重背包、混合背包、二维费用背包等)其基本类型为01背包问题。

一、01背包问题

N件物品,每件物品的重量和价值分别为 w[i], v[i], 把这些物品放到一个容量为W的背包中,求背包中物品的价值的最大值。

形式化定义: 

分析 
    最直观的思路是枚举背包中出现的所有可能的物品组合,然后计算它们的价值和,求最大值。如果直接采用递归搜索,则会有大量的冗余,通过记忆化搜索的方式可以进行改进(记忆 max[i][w], 前i中物品总重量达到w时候的最大价值,其中i和w又是递归函数的参数)。通过动态规划方式可以进一步改进。

动态规划要考虑最优子结构和无后效性,对于该问题,假设前i-1种物品在重量和约束W下得到了一个最大价值和M,那么在考虑前i种物品在重量和约束W‘下价值的最大值M‘时,我们只关心前i-1种物品的重量和W和最大价值和M,至于这前i-1种物品时如何选择对求解M‘无影响(因为M‘ = M + v[i],假设W + w[i] = W‘),即无后效性;同时,前i种物品在重量约束W‘下得到了价值的最大值,此时选取的物品集合为S,那么S中必然包含前i-1种物品在重量约束W(W = W‘或W = W‘-w[i])下得到最大值的组合(用反证法可以证明),即最优子结构。

因此考虑将问题规定为前i种物品总重量不超过w时,能够得到的最大价值 f[i][w]。此时有递推公式

    f[i][w] = max(f[i-1][w], f[i-1][w - w[i]] + v[i])
    //情形1.不选择第i种物品,那么前i种物品可得到的最大值为 f[i-1][w];
    //情形2.选择第i种物品,那么前i-1种物品可以使用 w -w[i]的空间存放,前i-1种物品可得最大价值 f[i-1][w-w[i]], 再加上第i种物品的价值 v[i]
    //在二者中间取最大值即可。

针对实际问题设置边界条件 
显然i>=0, w>=0. f[0][w]表示不选任何物品时,总重量不超过w,取得的价值和的最大值,显然为0,即f[0][w] = 0; f[i][0] 表示前i中物品,总重量和不超过0,取得价值和的最大值,由于问题中重量均为正整数,因此,显然为0,即 f[i][0] = 0.

优化 
    可以看出,f[i][xx]只和f[i-1][yy]有关,假设当前有一个一维数组,存放了前i-1种物品在某约束xx下的最大值,则可以直接求出前i种物品在某约束yy下的最大值。因此可以使用一维数组,来滚动实现二维数组,进行空间优化(通过画图更直观一些)。 
    f[i][w] = max(f[i-1][w], f[i-1][w - w[i]] + v[i]),改变为一维数组 f[w] = max(f[w], f[w-w[i]] + v[i]),在一维数组中考虑,重量w只和w及w-w[i]有关,因此要从后向前进行递推(若从前向后,则会导致被修改值x之后的值都是在新的x基础上(即前i种物品选择)被修改,不符合在前i-1种物品中进行选择的要求。

实现

for(int i = 0; i <= W; i ++){
    f[w] = 0;
}
for(int i = 1; i  <= n; i ++){
    for(int w = W; w >= w[i]; w --){
        f[w] = max(f[w], f[w-w[i]] + v[i]);
    }
}

复杂度分析 
    经过空间优化之后,空间复杂度为O(W), 时间复杂度为O(n*w).

二、完全背包问题

有N种物品,每种物品有个重量w[i]和价值v[i],每种物品可以选择无限多件。给定一个承重不超过 W的背包,求出背包中所放物品的价值和的最大值。

和01背包问题一样分析,设 f[i][w]表示前i种物品中选择总重不超过w的物品所能得到的价值和的最大值。有

f[i][w] = max{f[i-1][w - k*w[i]] + k*v[i]} | k属于[0, w/w[i]]

分析

    观察f[i][w] = max{f[i-1][w - k*w[i]] + k*v[i]} | k属于[0, w/w[i]],
    max{f[i-1][w - k*w[i]] + k*v[i]}
    = max{f[i-1][w], f[i-1][w-k*w[i]] + k*v[i]} | k属于[1, w/w[i]],
    令k = t+1, 则
    max{f[i-1][w-k*w[i]] + k*v[i]} | k属于[1, w/w[i]],
    = max{f[i-1][w-w[i] - t*w[i]] + t*v[i]} + v[i] | t属于[0, (w-w[i])/w[i]],
    = f[i-1][w - w[i]] + v[i]
    因此
    f[i][w] = max{f[i-1][w], f[i-1][w-w[i]] + v[i]}

因此递推公式化简为了 f[i][w] = max{f[i-1][w], f[i-1][w-w[i]] + v[i]} 
    对于这个公式,其实可以这样理解:在求f[i][w]需要求 max{f[i-1][w - k*w[i]] + k*v[i]}时候,而在求f[i][w-w[i]]时,已经求了max{f[i-1][w - k*w[i]] + k*v[i]}中的前面若干项,因此,知道了 f[i-1][w-w[i]]时,就可以继续推出f[i][w],而不需要再重复求max{f[i-1][w - k*w[i]] + k*v[i]}。

同样,可以利用一维数组进行空间优化,得到递推公式 
f[w] = max{f[w], f[w-w[i]] + v[i]

实现

for(int i = 0; i <= W; i ++){
    f[w] = 0;
}
for(int i = 0; i <= n; i ++){
    for(int w = w[i]; w <=  W; w ++){
    //这里递推从左向右!!因为就是需要前面的值更新对后面的值产生影响
        f[w] = max{f[w], f[w - w[i]] + v[i]};
    }
}

复杂度分析 
    在经过空间优化之后,空间复杂度为O(W),时间复杂度为O(n*W)

对比01背包和完全背包 
    都可以使用一维数组来降低空间复杂度,使用一维数组时,01背包的w顺序为从后向前(w ~ w - w[i]),而完全背包是从前向后(w-w[i] ~ w)。

三、多重背包问题

N种物品,第i种物品的重量值为w[i], 价值为v[i],且第i种物品有c[i]个。给定背包的承重W,求能放入背包中的物品总价值的最大值。

分析 
    仿照01背包和完全背包,可以得到递推公式f[i][w] = max{f[i-1][w - k*w[i]] + k*v[i] | v属于[0, min(c[i], w/w[i])]
    貌似和完全背包类似,是否可以用同样的方式进行优化变为一维递推形式呢? 答案是不行的。因为假设某个 w很大,w-w[i]也很大导致前i种物品重量全部相加都无法得到w-w[i],那么我们会将f[w-w[i]]设置为0,而 f[w] = max{f[w], f[w-w[i]] + v[i]} 就会变为 max{f[w], v[i]},显然大于0,不符合实际。 
    可以将第i种物品的c[i]个视为不同种类的物品,只不过他们的重量和价值相同,此时转化为01背包问题,即从 ∑n1c[i] 种物品,每种只有一件,选择总重量不超过W的物品组合,使得价值和最大。此时时间复杂度为 O(W*∑n1c[i]) 
    进一步优化,考虑一个定理

给定一个正整数n,可以用1, 2,... 2^(k-1), n - 2^k + 1这些数(约log(n)个)来表示[1,n]中的任何一个整数,其中k为使得n - 2^k + 1 > 0的最大的正整数

即二进制分解。。 
    利用这个定理,将c[i]个重量为w[i]、价值为v[i]的第i种物品分解为 {重量,价值} = {w[i], v[i]}, {2*w[i], 2*v[i]}, ...{2^(k-1)*w[i], 2^(k-1)*v[i]},{(c[i] - 2^k + 1)*w[i], (c[i] - 2^k + 1)v[i]}的一系列物品,然后转换为01背包问题。注意若存在新转换后的{w[k], v[k]}和原来的某种物品{w[j], v[j]}相同,不能将他们进行合并。
此时时间复杂度为O(W*(∑n1log2(c[i]))。 
实现

int weight[MAX];
int value[MAX];
void Expand(int w, int v, int n, int& index){
    int k = 1;
    do{
        weight[index] = k*w;
        value[index ++] = k*v;
        k*=2;
    }while(k*2 < n);
    weight[index] = (n - k + 1)*w;
    value[index++] = (n - k + 1)*v;
}

四、混合背包问题

混合背包问题是N种物品中有些物品是只能选一件或者不选,有些物品是可以选任意件,有些物品有个数限制。在背包承重W的限制下,求能够得到的价值总和的最大值。 
    解决方法是将多重背包转换为01背包,然后解决01背包和完全背包的混合。

for(int i = 1; i <= n; i ++){
    if(物品i为01背包物品){
        for(int w = W; w >= w[i]; w --){
            f[w] = max{f[w], f[w - w[i]] + v[i]};
        }
    }else if(物品i为完全背包物品){
       for(int w = w[i]; w <= W; w ++){
            f[w] = max{f[w], f[w - w[i]] + v[i]};
        }
    }
}

五、二维费用的背包问题

N中物品,每件物品i都有两种不同的空间消耗,选择这件物品必须同时付出这两种代价。对于每种代价都有一个可付出的最大值(背包容量),问怎样选择物品使得总价值和最大。

分析 
    和一维约束没什么区别,只不过将状态表示增加一个维度, 
f[i][w1][w2] = max{f[i-1][w], f[i-1][w1 - w1[i]][w2 - w2[i] + v[i]},利用空间优化之后为 f[w1][w2] = max{f[w1][w2], f[w1-w1[i]][w2-w2[i]] + v[i]},在递推的时候同样w1,w2从后向前。

六、分组的背包问题

有N件物品和一个容量为V的背包。第i件物品的费用是Ci,价值是Wi。这些物品被划分为K组,每组中的物品互相冲突,最多选一件。求解将哪些物品装入背包可使这些物品的费用总和不超过背包容量,且价值总和最大。

分析 
    这个问题变成了每组物品有若干种策略:是选择本组的某一件,还是一件都不选。也就是说设F [k, v]表示前k组物品花费费用v能取得的最大权值,则有:

for k = 1 to K //组
    for w = W to 0  //承重
        for item i in group k   //组内的每个物品
            f[w] = max{f[w], f[w - Ci] + Wi}

七、背包问题的相关问题

7.1 输出选取物品方案

如果不仅仅需要求出背包所承载物品的最大价值,还需要求出背包中选取的物品集合。则还需要维护另一个数组g[i][w], 若 f[i][w] == f[i-1][w]表示第i件物品没有被选择,则令g[i][w] = 0;若 f[i][w] == f[i-1[w - w[i]] + v[i],则说明选择了第i件物品,则令g[i][w] = 1,这样在求解f数组的时候顺便求出g数组。

int i = n, w = W;
while(i >= 1){
    if (g[i][w] == 0){
        未选择第i件物品
    }else{
        选择了第i件物品
        w = w - w[i]
    }
    i --;
}
7.2 输出字典序最小的最优方案

字典序最小是指1...N号物品的选择方案排列出来以后字典序最小。一般而言,求一个字典序最小的最优方案,只需要在转移时注意策略。子问题的定义要修改一下:如果存在一个选了物品1的最优方案,那么答案一定包含物品1,原问题转化为一个背包容量为W-C1,物品为2....N的子问题。反之,如果答案不包括物品1,则转化成背包容量仍为W,物品为2....N的子问题。 
令 f[i][w] 表示从物品i,i+1...N中选择若干物品总重量不超过w,所能得到价值的最大值。则有递推公式:f[i-1][w] = max{f[i][w], f[i][w-w[i-1]]},若f[i][w] == f[i][w-w[i-1]],则选择第i件物品。这样在递推完成之后,编号越小的物品就被记录下来,而同样优秀的字典序较大的方案被“覆盖”。

7.3 最优方案总数
g[0][0] = 1
for(int i = 1; i <= N; i ++){
    for(int w = 0; w <= W; w ++){
        f[i][w] = max{f[i-1][w], f[i-1][w-w[i]]};
        g[i][w] = 0;
        if(f[i][w] == f[i-1][w]){
            g[i][w] += g[i-1][w];
        }
        if(f[i][w] == f[i-1][w-w[i]] + v[i]){
        //可能选择不选择第i种物品得到结果相同,这样方案总数要相加
            g[i][w] += g[i-1][w-w[i]];
        }
    }
}
7.4 求第k优解

对于求次优解、第K优解类的问题,如果相应的最优解问题能写出状态转移方程、用动态规划解决,那么求次优解往往可以相同的复杂度解决,第K优解则比求最优解的复杂度上多一个系数K。

基本思想是,将每个状态都表示成有序队列,将状态转移方程中的max/min转化成有序队列的合并。 
    用f[i][w][k]表示前i种物品中选择总重量不超过w的物品,得到的价值和的第k大的值,则可以推出 
f[i][w][k] = Kth_Of_{f[i-1][w][1...k] + f[i-1][w-w[i]][1...k] + v[i]} 
    在两个大小为k的有序数组中,选择两个数组所有数字共同的第k大的数字。使用归并排序即可。

for(int i = 1; i <= n; i ++){
    for(int w = w[i]; w <= W; w ++){
        int t = 0, n1 = 0, n2 = 0;
        while(t < k){
            if(f[i-1][w][n1] > f[i-1][w-w[i]][n2] + v[i]){
                f[i][w][k] = f[i-1][w][n1++];
            }else{
                k_th_q = 2;
                f[i][w][k] = f[i-1][w - w[i]][n2 ++] + v[i];
            }
            t ++;
       }
    }
}
//时间复杂度为O(nWk);

参考

《背包九讲》

时间: 2024-12-29 12:09:49

动态规划——背包问题的相关文章

nyist oj 289 苹果 (动态规划——背包问题)

苹果 时间限制:3000 ms  |  内存限制:65535 KB 难度:3 描述 ctest有n个苹果,要将它放入容量为v的背包.给出第i个苹果的大小和价钱,求出能放入背包的苹果的总价钱最大值. 输入 有多组测试数据,每组测试数据第一行为2个正整数,分别代表苹果的个数n和背包的容量v,n.v同时为0时结束测试,此时不输出.接下来的n行,每行2个正整数,用空格隔开,分别代表苹果的大小c和价钱w.所有输入数字的范围大于等于0,小于等于1000. 输出 对每组测试数据输出一个整数,代表能放入背包的苹

动态规划——背包问题入门

动态规划--背包问题入门 1. 01背包 概述 给出N个物品,每个物体都具有一定的体积和价值,而每个物体都只有一个. 拥有一个V体积的背包,问应如何装包才能使背包中物体的总价值最大? 解题思路 背包问题是典型的动态规划类题目,而动态规划是典型的通过规律找出正解的方法.所以解题思路的关键在于如何寻找不同数据之间的关系(状态转移). 直接描述不方便解释,我们以例题为例: (注:01背包问题不涉及因为物体的形状.大小等而如何放入背包的问题,只是单纯考虑体积和价值.我们可以理解成每个物体都是液体,而这个

oj算法----动态规划----背包问题

oj算法----动态规划----背包问题 1.动态规划 1.1概念 动态规划(dynamic programming)是运筹学的一个分支,是求解决策过程(decision process)最优化的数学方法 1.2性质 动态规划一般用来处理最优解的问题.使用动态规划算法思想解决的问题一般具有最优子结构性质和重叠子问题这两个因素. <1> 最优子结构 一个问题的最优解包含其子问题的最优解,这个性质被称为最优子结构性质 <2> 重叠子问题 递归算法求解问题时,每次产生的子问题并不总是新问

动态规划-背包问题 Knapsack

2018-03-15 13:11:12 背包问题(Knapsack problem)是一种组合优化的NP完全问题.问题可以描述为:给定一组物品,每种物品都有自己的重量和价格,在限定的总重量内,我们如何选择,才能使得物品的总价格最高.问题的名称来源于如何选择最合适的物品放置于给定背包中. 相似问题经常出现在商业.组合数学,计算复杂性理论.密码学和应用数学等领域中. 一.0/1背包问题 背包问题是个NPC问题,01背包可以通过动态规划算法在伪多项式时间内给出解. 0/1背包问题的特点是,每种物品仅仅

动态规划——背包问题python实现(01背包、完全背包、多重背包)

目录 01背包问题 完全背包问题 多重背包问题 参考: 背包九讲--哔哩哔哩 背包九讲 01背包问题 01背包问题 描述: 有N件物品和一个容量为V的背包. 第i件物品的体积是vi,价值是wi. 求解将哪些物品装入背包,可使这些物品的总体积不超过背包流量,且总价值最大. 二维动态规划 f[i][j] 表示只看前i个物品,总体积是j的情况下,总价值最大是多少. result = max(f[n][0~V]) f[i][j]: 不选第i个物品:f[i][j] = f[i-1][j]; 选第i个物品:

动态规划-背包问题

1.P1060 开心的金明 https://www.luogu.org/problemnew/solution/P1164 #include <stdio.h> #include <memory.h> #include <math.h> #include <string> #include <vector> #include <set> #include <stack> #include <queue> #in

动态规划背包问题 洛谷P1064 金明的预算方案

P1064 金明的预算方案 题目描述 金明今天很开心,家里购置的新房就要领钥匙了,新房里有一间金明自己专用的很宽敞的房间.更让他高兴的是,妈妈昨天对他说:"你的房间需要购买哪些物品,怎么布置,你说了算,只要不超过N元钱就行".今天一早,金明就开始做预算了,他把想买的物品分为两类:主件与附件,附件是从属于某个主件的,下表就是一些主件与附件的例子: 主件 附件 电脑 打印机,扫描仪 书柜 图书 书桌 台灯,文具 工作椅 无 如果要买归类为附件的物品,必须先买该附件所属的主件.每个主件可以有

【动态规划】01背包问题_两种解法

问题描述 0-1背包问题:给定\(n\)种物品和一背包.物品i的重量是\(w_i\),其价值为\(v_i\),背包的容量为\(C\).问:应该如何选择装入背包的物品,使得装人背包中物品的总价值最大? 在选择装人背包的物品时,对每种物品\(i\)只有两种选择,即装人背包或不装入背包.不能将物品\(i\)装入背包多次,也不能只装入部分的物品\(i\).因此,该问题称为0-1背包问题. 此问题的形式化描述是,给定\(C>0\),\(w_i>0\),\(v_i>0\),\(1≤i≤n\),要求找

第十六章 贪心算法——0/1背包问题

1.问题描述: 给定n种物品和一背包.物品i的重量是wi,其价值为vi,背包的容量为C.问:应如何选择装入背包的物品,使得装入背包中物品的总价值最大? 形式化描述:给定c >0, wi >0, vi >0 , 1≤i≤n.要求找一n元向量(x1,x2,…,xn,), xi∈{0,1}, ∋ ∑ wi xi≤c,且∑ vi xi达最大.即一个特殊的整数规划问题. 2.最优性原理: 设(y1,y2,…,yn)是 (3.4.1)的一个最优解.则(y2,…,yn)是下面相应子问题的一个最优解: