01背包
[USACO07DEC] Charm Bracelet S - 洛谷
现在有一个背包容量为t,有n个物品,每个物品有自己的重量wi,价值vi。
dp[i][j]表示前i个物品任意选择,在容量不超过j最大价值。
所以dp[n][t]表示答案。
显然dp[0][i]=0
对于每一个物品 有两种情况
1.不选:dp[i][j]=dp[i-1][j] :dp[i][j]表示前i个物品选不超过j的最大价值,i物品你又不选,是不是就相当于前i-1个物品任意选,因为没选,所以重量也不增加.
2.选:有个前提:背包能装下.例如现在容量不能超过j,你如果要装这个物品,必须有j>=w[i],
dp[i][j]=max(dp[i-1][j],dp[i-1][j-w[i]]+v[i])1
核心代码:
//i:当前物品编号
for (int i = 1; i <= m; i++)
{
//j:不超过j的容量
for (int j = 0; j <= t; j++)
{
//i物品不要
dp[i][j] = dp[i - 1][j];
//i物品要
//要i物品 就要承受cost[i]的容量 如果不超过j的容量
if (j - cost[i] >= 0)
{
dp[i][j] = max(dp[i][j], dp[i-1][j - cost[i]] + value[i]);
}
}
}
再来解释一下小问题
例如容量50
物品1:重量10 价值10
物品2:重量50 价值50
dp[1][50-10]=10没问题
dp[2][50-50]=10也没问题 前两个自由选择 容量不超 选择第一个
dp[2][50]=50 :dp[i][j] = max(dp[i][j], dp[i-1][j - cost[i]] + value[i]);
相当于我们选了物品2 加上对应的value 此时物品2不能再选 并且容量下降cost[i]
所以是dp[i-1][j-cost[i]]+value[i]
这里是dp[2][50]=dp[1][0]+value[i]
依赖上面的格子和左上(可能有一段距离的格子)
你可能会有这么一个问题:i位置拿还是不拿,肯定拿啊,因为拿了价值就会增加,但是有容量作为限制条件,只能就是
1:不拿。2:拿,但是前面一部分就不能再拿了,因为拿就要给这个物品腾出w[i]的空间
当来到第i个物品,当空间限制在j时
2:拿:保证限制的空间大于等于物品容量:dp[i-1][j - cost[i]] + value[i] 这也就代表着把前面一些物品不拿了,拿的可能是1,2 i
1:不拿:可能拿的1 2 3 i-1
二者PK一下我们就能决定 是拿(1,2,i)好还是(1,2,3)好
空间压缩
dp数组的大小开的是容量m的大小
这个暂时我不理解
for (int i = 1; i <= n; i++)
{
for (int j = m; j >= w[i]; j--)
{
dp[j] = max(dp[j - w[i]] + d[i], dp[j]);
}
}
cout << dp[m];
不过要说一下,为什么是从右向左
因为如果左->右
注意此时dp是一维,上图是想象中。(1,2)受到上面和左上影响,所以改掉他。但是右边一个如果要用蓝色箭头呢?
根据二维 dp 的状态转移方程,这个左边指的是第 i-1 行的左边,如果你从 左到右就先把 dp[j-cost[i]]给覆盖掉了,你再计算dp[j] 的时候读到的就是第i行的而不是第i-1行
完全背包
也就是一个物品能取任意次
还是两种情况1:不拿 2:拿
如果不拿dp[i][j]=dp[i-1][j]
如果拿dp[i][j]=dp[i][j-cost[i]]
上图是01背包 拿的情况,完全体背包拿了这个物品,还能拿,所以...
然后完全体背包的状态转移方程是
dp[i][j]=max(dp[i-1][j],dp[i][j-cost[i]])
for (int i = 1; i <= m; i++) {
for (int j = 0; j <= t; j++) {
dp[i][j] = dp[i - 1][j];
if (j - cost[i] >= 0) {
dp[i][j] = max(dp[i][j], dp[i][j - cost[i]] + val[i]);
}
}
}
空间压缩
for (int i = 1; i <= m; i++) {
for (int j = cost[i]; j <= t; j++) {
dp[j] = max(dp[j], dp[j - cost[i]] + val[i]);
}
}
多重背包
n个物品,容量为t,每个物品有weight,value,cnt
最普通尝试:
每个物品拿k个0<=k<=cnt[i]
for (int i = 1; i <= n; i++) {
for (int j = 0; j <= t; j++) {
dp[i][j] = dp[i - 1][j];
for (int k = 1; k <= cnt[i] && weight[i] * k <= j; k++) {
dp[i][j] = max(dp[i][j], dp[i - 1][j - k * weight[i]] + k * value[i]);
}
}
}
空间压缩
for (int i = 1; i <= n; i++) {
for (int j = t; j >= 0; j--) {
for (int k = 1; k <= c[i] && weight[i] * k <= j; k++) {
dp[j] = max(dp[j], dp[j - k * weight[i]] + k * value[i]);
}
}
}
当然会超时,现在用二进制分组优化
例如现在有一堆物品:重量=50W,价值=50V,数量=50;
根据2的次方分组,分成这样
现在有6个物品,每个物品有weight,value
一眼01背包,这是由于,对于i号物品,有cnt个,(0—cnt)范围内任意的数字都能用分组后来表示;
例如这个物品我想取10个,对应上图的(2+3),取11个,对应上图(0+2+3).
然后使用01背包就好啦
值得注意的是 题目给你n堆物品,dp要开大于n的长度
分组
int m = 0;
for (int i = 1; i <= n; i++)
{
int v, w, cnt;
cin >> v >> w >> cnt;
// 二进制分组
for (int k = 1; k <= cnt; k <<= 1)
{
value[++m] = k * v;
weight[m] = k * w;
cnt -= k;
}
if (cnt > 0)
{
value[++m] = cnt * v;
weight[m] = cnt * w;
}
}
状态压缩
for (int i = 1; i <= m; i++)
{
for (int j = t; j >= weight[i]; j--)
{
dp[j] = max(dp[j], dp[j - weight[i]] + value[i]);
}
}
完整代码
#include<iostream>
#include<vector>
#include<map>
#include<algorithm>
#include<cmath>
#include<string>
#define int long long
const int N = 4e4 + 10;
using namespace std;
int weight[N];
int value[N];
int dp[N];
int m = 0;
signed main() {
int t, n;
cin >> n >> t;
int m = 0;
for (int i = 1; i <= n; i++)
{
int v, w, cnt;
cin >> v >> w >> cnt;
// 整个文件最重要的逻辑 : 二进制分组
for (int k = 1; k <= cnt; k <<= 1)
{
value[++m] = k * v;
weight[m] = k * w;
cnt -= k;
}
if (cnt > 0)
{
value[++m] = cnt * v;
weight[m] = cnt * w;
}
}
for (int i = 1; i <= m; i++)
{
for (int j = t; j >= weight[i]; j--)
{
dp[j] = max(dp[j], dp[j - weight[i]] + value[i]);
}
}
cout << dp[t] << endl;
return 0;
}
分组背包
有n个物品,还是重量和价值,不一样的是,某些物品是一组的,每个组你只能拿一个物品
dp[i][j]表示前面i组里面选了
两种情况:
1:我不选这个组dp[i][j]=dp[i-1][j]
2:选这个组:选这个组的谁好呢
这题,重点是去描述每个组的物品
这段代码起到了输入的作用,还有知道了一共有多少组,每个组有几个物品,每个组每个物品的下标.
例如c[5]=4表示5组有4个物品,二维数组g[][]存储下标
//n:n个物品
//m:m容量
int n, m, x, t = 0;
cin >> m >> n;
for (int i = 1; i <= n; i++)
{
cin >> a[i] >> b[i] >> x;
//t:有多少组
//这道题的x是不会跳的 只会是连续的
t = max(t, x);
//表示x组有多少物品
c[x]++;
//表示x组的c[x]物品的下标位置
g[x][c[x]] = i;
}
而下标是这么得到的:
输入1 1 1,t=1,c[1]=1,g[1][c[1]]=1--->g[1][1]=1--->一号物品
输入2 2 2,t=2,c[2]=1,g[2][c[2]]=2--->g[2][2]=2--->二号物品
输入3 2 1,t=1,c[1]=2,g[1][c[1]]=3--->g[1][2]=3--->三号物品
紧接着,遍历每一组,注意这里顺序很重要不能改(我不知道为什么)
for (int i = 1; i <= t; i++) {
for (int j = m; j >= 0; j--) {
for (int k = 1; k <= c[i]; k++) {
//a[g[i][k]]表示第i组第k个物品的重量
if (j >= a[g[i][k]]) {
//b[g[i][k]]表示第i组第k个物品的价值
dp[j] = max(dp[j], dp[j - a[g[i][k]]] + b[g[i][k]]);
}
}
}
}
完整版
#include<iostream>
#include<algorithm>
#include<cstdio>
#include<queue>
#define int long long
using namespace std;
const int N = 1e4+ 10;
int a[N], b[N], c[N];
int dp[N];
int g[N][N];
signed main()
{
//n:n个物品
//m:m容量
int n, m, x, t = 0;
cin >> m >> n;
for (int i = 1; i <= n; i++)
{
cin >> a[i] >> b[i] >> x;
//t:有多少组
//这道题的x是不会跳的 只会是连续的
t = max(t, x);
//表示x组有多少物品
c[x]++;
//表示x组的c[x]物品的下标位置
g[x][c[x]] = i;
}
for (int i = 1; i <= t; i++) {
for (int j = m; j >= 0; j--) {
for (int k = 1; k <= c[i]; k++) {
//a[g[i][k]]表示第i组第k个物品的重量
if (j >= a[g[i][k]]) {
//b[g[i][k]]表示第i组第k个物品的价值
dp[j] = max(dp[j], dp[j - a[g[i][k]]] + b[g[i][k]]);
}
}
}
}
cout << dp[m] << endl;
return 0;
}