0x52. 动态规划 - 背包(习题详解 × 19)
Posted 繁凡さん
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了0x52. 动态规划 - 背包(习题详解 × 19)相关的知识,希望对你有一定的参考价值。
本系列博客是《算法竞赛进阶指南》的学习笔记,包含书中的部分重要知识点、例题解题报告及我个人的学习心得和对该算法的补充拓展,仅用于学习交流和复习,无任何商业用途。博客中部分内容来源于书本和网络 ,由我个人整理总结。部分内容由我个人编写而成,如果想要有更好的学习体验或者希望学习到更全面的知识,请于京东搜索购买正版图书:《算法竞赛进阶指南》——作者李煜东,强烈安利,好书不火系列,谢谢配合。
%
学习笔记目录链接: 学习笔记目录链接
%
整理的算法模板合集: ACM模板
%
点我看算法全家桶系列!!!
0x52. 动态规划 - 背包
0x52.1 0 / 1 0/1 0/1 背包
Template
给定 N N N 个物品,其中第 i i i 个物品的体积为 V i V_i Vi,价值为 W i W_i Wi,有一个容积为 M M M 的背包, 求选择一些物品放入背包,使得物品的总体积不超过 M M M 的前提下,物品的价值总和最大,即背包不一定装满。
Solution
显然是一个线性DP,我们选择已处理的物品数和当前总体积作为DP的阶段
设
f
[
i
,
j
]
f[i, j]
f[i,j] 从前
i
i
i 个物品中选出总体积
j
j
j 物品放入背包的最大价值。
f
[
i
]
[
j
]
=
f
[
i
−
1
]
[
j
]
f[i][j] = f[i - 1][j]
f[i][j]=f[i−1][j] 不选择第
i
i
i 个物品
f
[
i
]
[
j
]
=
max
{
f
[
i
]
[
j
−
v
[
i
]
]
+
w
[
i
]
}
f[i][j] = \\max\\{f[i][j - v[i]] + w[i]\\}
f[i][j]=max{f[i][j−v[i]]+w[i]} 选择第
i
i
i 个物品
初始化 f [ 0 , 0 ] = 0 f[0, 0] = 0 f[0,0]=0
可以写出暴力转移代码:
f[maxn][maxn];
memset(f, 0xcf, sizeof f);
f[0][0] = 0;
for (int i = 1; i <= n; ++ i) {
for (int j = 0; j <= m; ++ j)
f[i][j] = f[i - 1][j];
for (int j = v[i], j <= m; ++ j)
f[i][j] = max(f[i][j], f[i - 1][j - v[i]] + w[i]);
}
由于
f
[
i
]
[
]
f[i][]
f[i][] 仅与
f
[
i
−
1
]
[
]
f[i -1][]
f[i−1][] 有关,显然可以使用滚动数组优化。
由于转移方程中
f
[
i
]
[
j
]
=
f
[
i
−
1
]
[
j
]
f[i][j] = f[i - 1][j]
f[i][j]=f[i−1][j] ,每次需要做一次拷贝,因此在做滚动数组的时候不需要清空。
f[2][maxn];
for (int i = 1; i <= n; ++ i) {
for (int j = 0; j <= m; ++ j)
f[i & 1][j] = f[i - 1 & 1][j];
for (int j = v[i]; j <= m; ++ j)
f[i & 1][j] = max(f[i & 1][j], f[i - 1 & 1][j - v[i]] + w[i]);
}
int ans = 0;
for (int j = 1; j <= m; ++ j)
ans = max(ans, f[n & 1][j]);
由于滚动数组的时候每次仅需拷贝无需清空,所以我们发现可以直接去掉第一维,在枚举到第
i
i
i 个物品的时候
f
[
j
]
f[j]
f[j] 表示背包中放入总体积为
j
j
j 的物品的最大价值和。但是由于 0/1 背包每个物品仅能使用一次,所以我们需要
倒序枚举,防止
f
[
i
]
[
j
]
f[i][j]
f[i][j] 从
f
[
i
]
[
j
−
v
[
i
]
]
f[i][j - v[i]]
f[i][j−v[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]);
int ans = 0;
for (int j = 1; j <= m; ++ j)
ans = max(ans, f[j]);
Problem A. 数字组合
AcWing 278
给定 N N N 个正整数,从中选出若干个数,使得它们的和是 M M M ,求有多少种选择方案。
1 ≤ N ≤ 100 , 1 ≤ M ≤ 1000 1\\le N \\le 100, 1\\le M \\le 1000 1≤N≤100,1≤M≤1000
Solution
显然是一个 0/1 背包的模型求总方案数,我们将转移时的 max \\max max 改为求和即可。
f [ i , j ] f[i, j] f[i,j] 表示取前 i i i 个数字,和为 j j j 的方案数。这里相当于必须装满的背包。
Code
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 6;
int n, m, s, t;
int a[N];
int f[N];
int main()
{
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; ++ i)
scanf("%d", &a[i]);
f[0] = 1;
for (int i = 1; i <= n; ++ i)
for (int j = m; j >= a[i]; -- j)
f[j] += f[j - a[i]];
cout << f[m] << endl;
return 0;
}
Problem B. 背包问题求具体方案
AcWing 12
有 N N N 件物品和一个容量是 V V V 的背包。每件物品只能使用一次。
第 i i i 件物品的体积是 v i v_i vi,价值是 w i w_i wi。
求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。
输出 字典序最小的方案。这里的字典序是指:所选物品的编号所构成的序列。物品的编号范围是 1 … N 1…N 1…N。
0
<
N
,
V
≤
1000
,
0
<
v
i
,
w
i
≤
1000
0<N,V≤1000,0<v_i,w_i≤1000
0<N,V≤1000,0<vi,wi≤1000
Solution
0/1 背包转移之后,倒序再遍历一遍输出方案即可。
因为要求按字典序输出,倒序循环枚举物品,正序循环输出方案即可。
Code
#include<cstdio>
#include<algorithm>
#include<iostream>
#include<cstring>
using namespace std;
const int N = 5007;
int n, m;
int f[N][N];
int v[N], w[N];
int main(){
scanf("%d%d", &n, &m);
for(int i = 1; i <= n; ++ i)
scanf("%d%d", &v[i], &w[i]);
for(int i = n; i >= 1; -- i){
for(int j = m; j >= 0; -- j){
f[i][j] = f[i + 1][j];
if(j >= v[i])
f[i][j] = max(f[i][j], f[i + 1][j - v[i]] + w[i]);
}
}
int j = m;
for(int i = 1; i <= n; ++ i){
if(j >= v[i] && f[i][j] == f[i + 1][j - v[i]] + w[i]){
printf("%d ", i);
j -= v[i];
}
}
puts("");
return 0;
}
Problem C. jury Compromise(多维度 0/1 背包,输出方案)
AcWing 280
从 N N N 个人中选择 M M M 个人。第 i i i个人有 a i , b i a_i,b_i a0x51.动态规划 - 线性DP(习题详解 × 10)