目录
背包问题
1. 01背包
采用空间压缩的代码(且用方案二的初始化方法):
2.完全背包
代码:
3. 多重背包
3.1 朴素办法:
3.2 利用二进制进行优化 O(V*n*logn(i))
代码:
3.3 单调队列优化
4.混合背包
代码
5.二维费用的背包问题
01背包的二维费用代码:
6.分组的背包问题
01背包的分组背包代码
7.01背包问题求最优方案数
方案1代码:
方案二代码:
8.背包问题求具体方案
输出字典序代码:
9.有依赖的背包问题
题目:
有N件物品和一个容量为V的背包。第i件物品的费用是c[i],价值是w[i]。求解将哪些物品装入背包可使这些物品的费用总和不超过背包容量,且价值总和最大。所有的背包问题都是先循环物品,再循环体积,再循环决策
https://www.cnblogs.com/jbelial/articles/2116074.html
每种物品仅有一件,可以选择放或不放。
记忆方法: 先枚举物品,再从大到小枚举体积
f[i][v]表示前i件物品放入一个容量为v的背包可以获得的最大价值。则其状态转移方程便是:
f[i][v]=max{f[i-1][v],f[i-1][v-c[i]]+w[i]}。
详细解释:“将前i件物品放入容量为v的背包中”这个子问题,若只考虑第i件物品的策略(放或不放),那么就可以转化为一个只牵扯前i-1件物品的问题。如果不放第i件物品,那么问题就转化为“前i-1件物品放入容量为v的背包中”;如果放第i件物品,那么问题就转化为“前i-1件物品放入剩下的容量为v-c[i]的背包中”,此时能获得的最大价值就是f [i-1][v-c[i]]再加上通过放入第i件物品获得的价值w[i]。
tips:1. 当要求容量v是恰好装满时,初始化f[0,0]=0 , f[0,1]=f[0,2]=.......=f[0,n]=-∞(说明:因为前一个0代表什么物品都不能选,而后一个v代表此时背包被占用容量恰好是V,那自然是不可能的,所以用负无穷表示没有方案可以满足要求。)
2. 当不要求恰好装满,初始化f[0,0]=f[0,1]=.......=f[0,n]=0 (因为这里的v表示背包最多能被占多少容量,f【0】【x】的含义是,什么物品都不能选,而背包最多能被放入总共容量为V的物品,此时的最大价值,自然是0)
所以如果用方案一的初始化方法,最大价值是 f【N】【0...V】的最大值,而如果用方案二的初始化方法,最大价值是f【N】【V】
#include
#include
using namespace std;
const int N = 1010;
int n, m;
int v[N], w[N];
int f[N];
int main()
{
cin >> n >> m; // n表示物品总数 ,m表示背包容量
for (int i = 1; i <= n; i ++ ) cin >> v[i] >> w[i]; //v[i]:第i件物品的体积 w[i]:第i件物品的价值
for (int i = 1; i <= n; i ++ )
for (int j = m; j >= v[i]; j -- ) //枚举体积的时候倒着来 从大到小
f[j] = max(f[j], f[j - v[i]] + w[i]);
cout << f[m] << endl;
return 0;
}
每种物品都有无限件可用
记忆方法: 先枚举物品,再从小到大枚举体积
从每种物品的角度考虑,与它相关的策略已并非取或不取两种,而是有取0件、取1件、取2件……等很多种。如果仍然按照解01背包时的思路,令f[i][v]表示前i种物品放入一个容量为v的背包的最大价值。仍然可以按照每种物品不同的策略写出状态转移方程,像这样:
f[i][v]=max{f[i-1][v-k*c[i]]+k*w[i]|0<=k*c[i]<= v}
解释:前i种物品放入一个容量为v的背包的最大价值等于:
- 我完全不用第i件物品, f【i-1】【v】
- 我只用1件第i件物品, f【i-1】【v-c【i】】+w【i】
- 我只用2件第i件物品, f【i-1】【v-2c【i】】+2w【i】
- ......
- 我只用k件第i件物品, f【i-1】【v-kc【i】】+kw【i】(k是总容量为v,最多能放几件第i件物品)
f【i】【v】就等于这么多种情况的最大值,而其实第2种到第5种情况他们之间的最大值,其实就是
f【i】【v-c【i】】+w【i】(假如v小于c【i】,值应该等于0)
所以f【i】【v】=max( f【i-1】【v】,(v-c【i】)>=0? f【i】【v-c【i】】+w【i】: 0)
再进一步,假如采用空间压缩技巧,只要第二个循环枚举容量的时候,从小到大(而不是从大到小),就ok了
(同样,初始化的方法,决定最大值是哪个)
#include
#include
using namespace std;
const int N = 1010;
int n, m;
int v[N], w[N];
int f[N];
int main()
{
cin >> n >> m;
for (int i = 1; i <= n; i ++ ) cin >> v[i] >> w[i];
for (int i = 1; i <= n; i ++ )
for (int j = v[i]; j <= m; j ++ ) // 从小到大枚举体积
f[j] = max(f[j], f[j - v[i]] + w[i]);
cout << f[m] << endl;
return 0;
}
有N种物品和一个容量为V的背包。第i种物品最多有n[i]件可用,每件费用是c[i],价值是w[i]。求解将哪些物品装入背包可使这些物品的费用总和不超过背包容量,且价值总和最大。
记忆方法:先枚举物品,再从大到小枚举体积,最后从1开始枚举第i件物品放几件(需要满足自身限制和体积限制)
令f[i][v]表示前i种物品恰放入一个容量为v的背包的最大权值,则:f[i][v]=max{f[i-1][v-k*c[i]]+ k*w[i]|0<=k<=n[i]}。复杂度是O(V*∑n[i])。 (其中k既要满足 kc【i】 tips:这里不能像完全背包一样进行优化,因为你无法知道f【i】【v-c【i】】有没有把第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]间的每一个整数,均可以用若干个系数的和表示。 这样就将第i种物品分成了O(log n[i])种物品,将原问题转化为了复杂度为O(V*∑log n[i])的01背包问题,是很大的改进。 (将第i种物品,用二进制的方法分成若干件物品,这若干件物品选与不选,恰好可以组成0..n[i]间的每一个整数) 有的物品只可以取一次(01背包),有的物品可以取无限次(完全背包),有的物品可以取的次数有一个上限(多重背包)。 解决方法:物品是什么类型的,就用什么方法求解。 具体的就是01背包用01背包的方法求(容量从大到小),完全背包用容量从小到大的方法求,而多重背包用容量从大到小,个数从1到极限求(也可以用二进制方法转为01背包) 二维费用的背包问题是指:对于每件物品,具有两种不同的费用;选择这件物品必须同时付出这两种代价;对于每种代价都有一个可付出的最大值(背包容量)。问怎样选择物品可以得到最大的价值。设这两种代价分别为代价1和代价2,第i件物品所需的两种代价分别为a[i]和b[i]。两种代价可付出的最大值(两种背包容量)分别为V和U。物品的价值为w[i]。 解决方法: 费用加了一维,只需状态也加一维即可。设f[i][v][u]表示前i件物品付出两种代价分别为v和u时可获得的最大价值。状态转移方程就是:f [i][v][u]=max{f[i-1][v][u],f[i-1][v-a[i]][u-b[i]]+w[i]}。而如果使用空间压缩,可以只使用两维数组 f【v】【u】:假如是01背包(物品只可以取一次时) 变量v和u采用从大到小的两重循环,当物品是完全背包问题时采用从小到大的两重循环。当物品有如多重背包问题时拆分物品。 物品总个数的限制: 问题: 算法 for 所有的i属于组k 注:优化 有N件物品和一个容量是 V 的背包。每件物品只能使用一次。 第 i 件物品的体积是 vi,价值是 wi。 求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。 输出 最优选法的方案数。注意答案可能很大,请输出答案模 10^9+7的结果。 解决方法: 用两个数组 f[i][v]意义同前述,g[i][v]表示这个子问题的最优方案的总数,则在求f[i][v]的同时求g[i][v]的伪代码如下: for i=1..N tips:这里的初始化方法也有两种 采用方案1初始化,最终答案就是g【m】,而采用方案2初始化最终的答案需要首先遍历f【i】,找到最大的值max_num,然后再遍历一遍f【i】,将等于max_num的f【i】的g【i】都加起来,就是最终的答案。 假如要求输出字典序最小的具体方案,那么需要逆序求商品 输出时的判断条件if(f[i][vol]==f[i+1][vol-v[i]]+w[i]); vol每次输出之后都要减去v[i],所以判断的时候vol-v[i]有可能是负数导致数组越界,所以加一句vol-v[i]>=0的判断。 判断要不要选的逻辑是,当前能选就选(因为是字典序最小) 问题描述: 有 N个物品和一个容量是 V 的背包。 物品之间具有依赖关系,且依赖关系组成一棵树的形状。如果选择一个物品,则必须选择它的父节点。 如下图所示: 如果选择物品5,则必须选择物品1和2。这是因为2是5的父节点,1是2的父节点。 每件物品的编号是 i,体积是 vi,价值是 wi,依赖的父节点编号是 pi。物品的下标范围是 1…N。 求解将哪些物品装入背包,可使物品总体积不超过背包容量,且总价值最大。 输出最大价值。 f【i】【j】,选节点i,并且所用体积是j,以i为根的子树的最大收益是多少 #include
3.2 利用二进制进行优化 O(V*n*logn(i))
代码:
#include
3.3 单调队列优化
4.混合背包
代码
//01 背包则直接放入数据容器中
多重背包则化解成 01 背包 放入数据容器中(见多重背包II习题 进行二进制优化)
完全背包也直接放入数据容器中
此刻数据容器vector[HTML_REMOVED] things;中就只有01背包和完全背包 那么就进行遍历处理
#include
5.二维费用的背包问题
有时,“二维费用”的条件是以这样一种隐含的方式给出的:最多只能取M件物品。这事实上相当于每件物品多了一种“件数”的费用,每个物品的件数费用均为1,可以付出的最大件数费用为M。换句话说,设f[v][m]表示付出费用v、最多选m件时可得到的最大价值,则根据物品的类型(01、完全、多重)用不同的方法循环更新,最后答案就是f[V][M]。
另外,如果要求“恰取M件物品”,则注意初始化方法,01背包的二维费用代码:
//类似01背包问题用滚动数组,那么这个题目就只用二维数组就行了.
#include
6.分组的背包问题
有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
f[v]=max{f[v],f[v-c[i]]+w[i]}
另外,显然可以对每组中的物品应用P02中“一个简单有效的优化”。
完全背包问题有一个很简单有效的优化,是这样的:若两件物品i、j满足c[i]<=c[j]且w[i]>=w[j],则将物品j去掉,不用考虑。这个优化的正确性显然:任何情况下都可将价值小费用高得j换成物美价廉的i,得到至少不会更差的方案。对于随机生成的数据,这个方法往往会大大减少物品的件数,从而加快速度。然而这个并不能改善最坏情况的复杂度,因为有可能特别设计的数据可以一件物品也去不掉。 01背包的分组背包代码
// 分组背包问题
#include
7.01背包问题求最优方案数
for v=0..V
f[i][v]=max{f[i-1][v],f[i-1][v-c[i]]+w[i]}
if(f[i][v]==f[i-1][v])
g[i][v]=g[i-1][v]
if(f[i][v]==f[i-1][v-c[i]]+w[i])
g[i][v] += g[i-1][v-c[i]] //因为可能两种方法都能达到最优
方案1代码:
#include
方案二代码:
8.背包问题求具体方案
输出字典序代码:
#include
9.有依赖的背包问题