背包问题
2016-05-31 20:07
218 查看
01背包
参考 http://blog.csdn.net/u010910642/article/details/51545365初始化的细节问题:
如果在初始化时除了 f[0]为 0 其它f[1..V]均设为−∞,这样就可以保证最终得到的 f[V]是一种恰好装满背包的最优解。
如果在初始化时将f[0..V]全部设为 0,则只希望价格尽量大,而不要求必须把背包装满。
完全背包
参考:http://blog.csdn.net/u010910642/article/details/51545564多重背包问题
题目
有N件物品和容量为V的背包。第i件物品最多使用n[i]件,每件费用为c[i], 价值是w[i].求解将哪些物品装入背包可使得这些物品的费用总和不超过背包容量,且价值最大.若用f[i][v]表示对容量为v的背包,处理完前i种物品后,背包内物品可达到的最大总价值,并记m[i] = min(n[i], j / v[i])。放入背包的第i种物品的数目可以是:0,1,2,…可得:
F[i][v]=maxF[i−1][v–k∗c[i]]+k∗w[i](0<=k<=m[i])
对公式进行如下调整:
假设d=c[i],a=j/d,b=j%d,即 v=a∗d+b,代入上述公式,并用k替换a−k得:
F[i][j]=maxF[i−1][b+k∗d]−k∗w[i]+a∗w[i](a–m[i]<=k<=a)
对F[i - 1][y] (y= b b+d b+2d b+3d b+4d b+5d b+6d … j)
F[i][j]就是求j的前面m[i] + 1个数对应的F[i - 1] [b + k * d] - k * w[i]的最大值,加上a * w[i],如果将F[i][j]前面所有的F[i - 1][b + k * d] – k * w 放入到一个队列,那么,F[i][j]就是求这个队列最大长度为m[i] + 1时,队列中元素的最大值,加上a * w[i]。因而原问题可以转化为:O(1)时间内求一个队列的最大值。
该问题可以这样解决:
① 用另一个队列B记录指定队列的最大值(或者记录最大值的地址),并通过下面两个操作保证队列B的第一个元素(或其所指向的元素)一定是指定队列的当前最大值。
② 当指定队列有元素M进入时,删除队列B中的比M小的(或队列B中所指向的元素小等于M的)所有元素,并将元素M(或其地址)存入队列B。
③ 当指定队列有元素M离开时,队列B中的第一个元素若与M相等(或队列B第一个元素的地址与M相等),则队列B的第一个元素也离队。
经过上述处理,可以保证队列B中的第一个元素(或其指向的元素)一定是所指定队列所有元素的最大值。显然队列B的元素(或其所指向的元素)是单调递减的,
const int max_n = 10; int V,N; int cost[max_n], value[max_n], num[max_n]; void pre_deal() { for (int i = 0; i < N; i++) if (V / cost[i] <= num[i]) num[i] = V / cost[i]; } void solution(){ for (int i = 0; i < N; i++) { deque<int> data, assist; // 数据队列(要求最大值得原始队列)和辅助队列(单调递减的数列) for (int j = 0; j < cost[i]; j++) // V/cost[i]的余数 b的值 { for (int k = 0; j + k*cost[i] <= V; k++) { // 从前往后,间隔为cost[i]遍历best数组 if (data.size() == num[i]) { // 数据队列的个数超过指定的个数 if (data.front() == assist.front()) assist.pop_front(); // 辅助队列最大元素(第一个)与要删除的元素相同,弹出 data.pop_front(); } int temp = best[j + k*cost[i]] - k*value[i]; // 要插入数据队列的值 data.push_back(temp); while (!assist.empty() && assist.back() < temp) assist.pop_back(); // 查找前边小余temp的值 assist.push_back(temp); best[j + k*cost[i]] = assist.front() + i*value[i]; // 对best赋值 } data.clear(); assist.clear(); } } }
混合三种背包
有N件物品和容量为V的背包。第i件物品最多使用n[i](可能只可以取1次,也可能无限次,也可能有限次)件,每件费用为c[i], 价值是w[i].求解将哪些物品装入背包可使得这些物品的费用总和不超过背包容量,且价值最大.代码:
const int max_n = 10; int V,N; int cost[max_n], value[max_n], num[max_n]; void pre_deal() { for (int i = 0; i < N; i++) if (V / cost[i] <= num[i]) num[i] = V / cost[i]; } void solution() { for (int i = 1; i <= N; i++) { if (num[i] == 1) // 01背包 for (int j = V; j - cost[i] >= 0; j--) best[j] = max(best[j], best[j - cost[i]] + value[i]); else if (num[i] == V / cost[i]) // 完全背包 for (int j = cost[i]; j <= V; j++) best[j] = max(best[j], best[j - cost[i]] + value[i]); else { deque<int> data, assist; for (int j = 0; j < cost[i]; j++) { for (int k = 0; j + k*cost[i] <= V; k++) { if (data.size() == num[i]) { if (data.front() == assist.front()) assist.pop_front(); data.pop_front(); } int temp = best[j + k*cost[i]] - k*value[i]; data.push_back(temp); while (!assist.empty() && assist.back() < temp) assist.pop_back(); assist.push_back(temp); best[j + k*cost[i]] = assist.front() + i*value[i]; // 对best赋值 } data.clear(); assist.clear(); } } } }
二维费用的背包问题
题目
二维费用的背包问题是指:对于每件物品,具有两种不同的费用;选择这件物品必须同时付出这两种代价;对于每种代价都有一个可付出的最大值(背包容量)。问怎样选择物品可以得到最大的价值。设这两种代价分别为代价I 和代价II,第i件物品所需的两种代价分别为 cost_I[i]和 cost_II[i]。两种代价可付出的最大值(两种背包容量)分别为 V 和 U。物品的价值为 value[i];
分析
设 f[i][v][u]表示前i件物品付出两种代价分别为 v 和 u 时可获得的最大价值。状态转移方程就是:f[i][v][u]=max{f[i−1][v][u],f[i−1][v−costI[i]][u−costII[i]]+value[i]}
用二维数组来解决这问题
分组的背包问题
问题
有N件物品和一个容量为 V 的背包。第i件物品的费用是 c[i]价值是 w[i]。这些物品被划分为若干组,每组中的物品互相冲突,最多选一件。求解将哪些物品装入背包可使这些物品的费用总和不超过背包容量,且价值总和最大。
算法
这个问题变成了每组物品有若干种策略:是选择本组的某一件,还是一件都不选。也就是说设 f[k][v]表示前 k 组物品花费费用 v 能取得的最大权值,则有:f[k][v]=max{f[k−1][v],f[k−1][v−c[i]]+w[i]|物品 i 属于第 k 组}
使用一维数组的伪代码如下:
for 所有的组 k for v=V..0 for 所有的 i 属于组 k f[v]=max{f[v],f[v-c[i]]+w[i]}
注意这里的三层循环的顺序, “for v=V..0” 这一层循环必须在“for 所有的 i 属于组 k” 之外。 这样才能保证每一组内的物品最多只有一个会被添加到背包中。
有依赖的背包问题
简化的问题
这种背包问题的物品间存在某种“ 依赖” 的关系。也就是说, i 依赖于 j,表示若选物品 i,则必须选物品 j。为了简化起见,我们先设没有某个物品既依赖于别的物品,又被别的物品所依赖;另外,没有某件物品同时依赖多件物品。
思路
按照背包问题的一般思路,仅考虑一个主件和它的附件集合。可是,可用的策略非常多,包括:一个也不选,仅选择主件,选择主件后再选择一个附件,选择主件后再选择两个附件„„无法用状态转移方程来表示如此多的策略。(事实上,设有 n 个附件,则策略有 2n+1 个,为指数级。)考虑到所有这些策略都是互斥的(也就是说,你只能选择一种策略),所以一个主件和它的附件集合实际上对应于分组背包问题中的一个物品组,每个选择了主件又选择了若干个附件的策略对应于这个物品组中的一个物品,其费用和价值都是这个策略中的物品的值的和。但仅仅是这一步转化并不能给出一个好的算法,因为物品组中的物品还是像原问题的策略一样多。
对于一个物品组中的物品,所有费用相同的物品只留一个价值最大的,不影响结果。所以,我们可以对主件 i 的“ 附件集合” 先进行一次01 背包,得到费用依次为 0..V-c[i]所有这些值时相应的最大价值f’[0..V-c[i]]。那么这个主件及它的附件集合相当于 V-c[i]+1 个物品的物品组,其中费用为 c[i]+k 的物品的价值为 f’[k]+w[i]。也就是说原来指数级的策略中有很多策略都是冗余的,通过一次 01 背包后,将主件 i 转化为 V-c[i]+1个物品的物品组,就可以直接应用分组背包问题的算法解决问题了。
较一般的问题
更一般的问题是:依赖关系以图论中“ 森林” 的形式给出(森林即多叉树的集合),也就是说,主件的附件仍然可以具有自己的附件集合,限制只是每个物品最多只依赖于一个物品( 只有一个主件)且不出现循环依赖。
解决这个问题仍然可以用将每个主件及其附件集合转化为物品组的方式。唯一不同的是,由于附件可能还有附件,就不能将每个附件都看作一个一般的 01 背包
中的物品了。若这个附件也有附件集合,则它必定要被先转化为物品组,然后用分组的背包问题解出主件及其附件集合所对应的附件组中各个费用的附件所对
应的价值。
事实上,这是一种树形 DP,其特点是每个父节点都需要对它的各个儿子的属性进行一次 DP 以求得自己的相关属性。这已经触及到了“ 泛化物品” 的思想,这个“ 依赖关系树” 每一个子树都等价于一件泛化物品,求某节点为根的子树对应的泛化物品相当于求其所有儿子的对应的泛化物品之和。
泛化物品的背包问题
定义
考虑这样一种物品,它并没有固定的费用和价值,而是它的价值随着你分配给它的费用而变化。这就是泛化物品的概念。更严格的定义之。在背包容量为 V 的背包问题中,泛化物品是一个定义域为 0..V,中的整数的函数 h,当分配给它的费用为 v 时,能得到的价值就是 h(v)。
这个定义有一点点抽象,另一种理解是一个泛化物品就是一个数组 h[0..V],给它费用 v,可得到价值 h[V]。
一个费用为 c 价值为 w 的物品,如果它是 01 背包中的物品,那么把它看成泛化物品,它就是除了 h(c)=w 其它函数值都为 0 的一个函数。如果它是完全背包中
的物品,那么它可以看成这样一个函数,仅当 v 被 c 整除时有 h(v)=v/c*w,其它函数值均为 0。如果它是多重背包中重复次数最多为 n 的物品,那么它对应的
泛化物品的函数有 h(v)=v/c*w 仅当 v 被 c 整除且 v/c<=n,其它情况函数值均为0。
一个物品组可以看作一个泛化物品 h。对于一个 0..V 中的 v,若物品组中不存在费用为 v 的的物品,则 h(v)=0,否则 h(v)为所有费用为 v 的物品的最大价值。
依赖的背包问题中每个主件及其附件集合等价于一个物品组,自然也可看作一个泛化物品。
泛化物品的和
如果面对两个泛化物品 h 和 l,要用给定的费用从这两个泛化物品中得到最大的价值,怎么求呢?事实上,对于一个给定的费用 v,只需枚举将这个费用如何分配给两个泛化物品就可以了。同样的,对于 0..V 的每一个整数 v,可以求得费用 v 分配到 h 和 l 中的最大价值 f(v)。也即
f(v)=max{h(k)+l(v−k)|0<=k<=v}
可以看到, f 也是一个由泛化物品 h 和 l 决定的定义域为 0..V 的函数,也就是说, f 是一个由泛化物品 h 和 l 决定的泛化物品。
由此可以定义泛化物品的和: h、 l 都是泛化物品,若泛化物品 f 满足
f(v)=max{h(k)+l(v−k)|0<=k<=v}
则称 f 是 h 与 l 的和,即 f=h+l。这个运算的时间复杂度取决于背包的容量,是 O(V^2)。
泛化物品的定义表明:在一个背包问题中,若将两个泛化物品代以它们的和,不影响问题的答案。事实上,对于其中的物品都是泛化物品的背包问题,求它的答
案的过程也就是求所有这些泛化物品之和的过程。设此和为 s,则答案就是s[0..V]中的最大值。
相关文章推荐
- 数据库中的事务是什么
- SQL常用易忘命令
- 重载与重写的区别
- Middle-题目117:98. Validate Binary Search Tree
- java继承关系
- Java读写文化总结
- Middle-题目116:224. Basic Calculator
- java类的构造方法特点
- #码神学习#第三天
- Middle-题目115:71. Simplify Path
- 机器学习中的相似性度量
- 工厂模式
- 英文标题首字母大写规则
- (三)MyBatis源码解析之配置文件
- 设计模式<依赖倒置>
- HTML标记语言--常用标记
- 欢迎使用CSDN-markdown编辑器
- Middle-题目114:3. Longest Substring Without Repeating Characters
- (二)MyBatis源码解析之配置文件
- 【一天一道LeetCode】#75. Sort Colors