背包九讲 https://www.kancloud.cn/kancloud/pack/70124
AcWing https://www.acwing.com/problem/
说来惭愧,末考结束留校训练已经好几天了,我这几天的状态差到如咸鱼一般啥也学不进,就把背包九讲给看了一遍。
如上是入门动态规划基础之背包问题的一个个人认为很好的学习途径,以下各例题来源于yxc聚聚创办的AcWing。(每道题都有视频讲解哦~)
01背包问题
有 NN 件物品和一个容量是 VV 的背包。每件物品只能使用一次。
第 ii 件物品的体积是 vivi,价值是 wiwi。
求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。
输出最大价值。
输入格式
第一行两个整数,N,VN,V,用空格隔开,分别表示物品数量和背包容积。
接下来有 NN 行,每行两个整数 vi,wivi,wi,用空格隔开,分别表示第 ii 件物品的体积和价值。
输出格式
输出一个整数,表示最大价值。
数据范围
0<N,V≤10000<N,V≤1000
0<vi,wi≤10000<vi,wi≤1000
输入样例
4 5
1 2
2 4
3 4
4 5
输出样例:
8
题解
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 1e3 + 10;
int c[maxn], w[maxn], dp[maxn];
inline const int read()
{
int x = 0, f = 1; char ch = getchar();
while (ch < '0' || ch > '9') { if (ch == '-') f = -1; ch = getchar(); }
while (ch >= '0' && ch <= '9') { x = (x << 3) + (x << 1) + ch - '0'; ch = getchar(); }
return x * f;
}
int main()
{
int n = read(), m = read();
for (int i = 1; i <= n; i++)
{
c[i] = read();
w[i] = read();
}
for (int i = 1; i <= n; i++)
for (int j = m; j >= c[i]; j--)
dp[j] = max(dp[j], dp[j - c[i]] + w[i]);
printf("%d\n", dp[m]);
return 0;
}
完全背包问题
有 NN 种物品和一个容量是 VV 的背包,每种物品都有无限件可用。
第 ii 种物品的体积是 vivi,价值是 wiwi。
求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。
输出最大价值。
输入格式
第一行两个整数,N,VN,V,用空格隔开,分别表示物品种数和背包容积。
接下来有 NN 行,每行两个整数 vi,wivi,wi,用空格隔开,分别表示第 ii 种物品的体积和价值。
输出格式
输出一个整数,表示最大价值。
数据范围
0<N,V≤10000<N,V≤1000
0<vi,wi≤10000<vi,wi≤1000
输入样例
4 5
1 2
2 4
3 4
4 5
输出样例:
10
题解
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 1e3 + 10;
int c[maxn], w[maxn], dp[maxn];
inline const int read()
{
int x = 0, f = 1; char ch = getchar();
while (ch < '0' || ch > '9') { if (ch == '-') f = -1; ch = getchar(); }
while (ch >= '0' && ch <= '9') { x = (x << 3) + (x << 1) + ch - '0'; ch = getchar(); }
return x * f;
}
int main()
{
int n = read(), m = read();
for (int i = 1; i <= n; i++)
{
c[i] = read();
w[i] = read();
}
for (int i = 1; i <= n; i++)
for (int j = c[i]; j <= m; j++)
dp[j] = max(dp[j], dp[j - c[i]] + w[i]);
printf("%d\n", dp[m]);
return 0;
}
多重背包问题 I
有 NN 种物品和一个容量是 VV 的背包。
第 ii 种物品最多有 sisi 件,每件体积是 vivi,价值是 wiwi。
求解将哪些物品装入背包,可使物品体积总和不超过背包容量,且价值总和最大。
输出最大价值。
输入格式
第一行两个整数,N,VN,V,用空格隔开,分别表示物品种数和背包容积。
接下来有 NN 行,每行三个整数 vi,wi,sivi,wi,si,用空格隔开,分别表示第 ii 种物品的体积、价值和数量。
输出格式
输出一个整数,表示最大价值。
数据范围
0<N,V≤1000<N,V≤100
0<vi,wi,si≤1000<vi,wi,si≤100
输入样例
4 5
1 2 3
2 4 1
3 4 3
4 5 2
输出样例:
10
题解
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 1e2 + 10;
int c[maxn], w[maxn], s[maxn], dp[maxn];
inline const int read()
{
int x = 0, f = 1; char ch = getchar();
while (ch < '0' || ch > '9') { if (ch == '-') f = -1; ch = getchar(); }
while (ch >= '0' && ch <= '9') { x = (x << 3) + (x << 1) + ch - '0'; ch = getchar(); }
return x * f;
}
int main()
{
int n = read(), m = read();
for (int i = 1; i <= n; i++)
{
c[i] = read();
w[i] = read();
s[i] = read();
}
for (int i = 1; i <= n; i++)
for (int j = m; j >= 0; j--)
for (int k = 1; k <= s[i] && c[i] * k <= j; k++)
dp[j] = max(dp[j], dp[j - c[i] * k] + w[i] * k);
printf("%d\n", dp[m]);
return 0;
}
多重背包问题 II
有 NN 种物品和一个容量是 VV 的背包。
第 ii 种物品最多有 sisi 件,每件体积是 vivi,价值是 wiwi。
求解将哪些物品装入背包,可使物品体积总和不超过背包容量,且价值总和最大。
输出最大价值。
输入格式
第一行两个整数,N,VN,V,用空格隔开,分别表示物品种数和背包容积。
接下来有 NN 行,每行三个整数 vi,wi,sivi,wi,si,用空格隔开,分别表示第 ii 种物品的体积、价值和数量。
输出格式
输出一个整数,表示最大价值。
数据范围
0<N≤10000<N≤1000
0<V≤20000<V≤2000
0<vi,wi,si≤20000<vi,wi,si≤2000
提示:
本题考查多重背包的二进制优化方法。
输入样例
4 5
1 2 3
2 4 1
3 4 3
4 5 2
输出样例:
10
题解
采用二进制优化
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 2e3 + 10;
int dp[maxn];
struct item { int c, w; };
vector<item> vec;
inline const int read()
{
int x = 0, f = 1; char ch = getchar();
while (ch < '0' || ch > '9') { if (ch == '-') f = -1; ch = getchar(); }
while (ch >= '0' && ch <= '9') { x = (x << 3) + (x << 1) + ch - '0'; ch = getchar(); }
return x * f;
}
int main()
{
int n = read(), m = read();
for (int i = 1; i <= n; i++)
{
int c = read(), w = read(), s = read();
for (int j = 1; j <= s; j <<= 1)
{
s -= j;
vec.push_back(item{ c * j, w * j });
}
if (s > 0) vec.push_back(item{ c * s, w * s });
}
for (auto it : vec)
for (int i = m; i >= it.c; i--)
dp[i] = max(dp[i], dp[i - it.c] + it.w);
printf("%d\n", dp[m]);
return 0;
}
多重背包问题 III
有 NN 种物品和一个容量是 VV 的背包。
第 ii 种物品最多有 sisi 件,每件体积是 vivi,价值是 wiwi。
求解将哪些物品装入背包,可使物品体积总和不超过背包容量,且价值总和最大。
输出最大价值。
输入格式
第一行两个整数,N,VN,V (0<N≤1000(0<N≤1000, 0<V≤20000)0<V≤20000),用空格隔开,分别表示物品种数和背包容积。
接下来有 NN 行,每行三个整数 vi,wi,sivi,wi,si,用空格隔开,分别表示第 ii 种物品的体积、价值和数量。
输出格式
输出一个整数,表示最大价值。
数据范围
0<N≤10000<N≤1000
0<V≤200000<V≤20000
0<vi,wi,si≤200000<vi,wi,si≤20000
提示
本题考查多重背包的单调队列优化方法。
输入样例
4 5
1 2 3
2 4 1
3 4 3
4 5 2
输出样例:
10
题解
采用单调队列优化
To be done
混合背包问题
有 NN 种物品和一个容量是 VV 的背包。
物品一共有三类:
- 第一类物品只能用1次(01背包);
- 第二类物品可以用无限次(完全背包);
- 第三类物品最多只能用 sisi 次(多重背包);
每种体积是 vivi,价值是 wiwi。
求解将哪些物品装入背包,可使物品体积总和不超过背包容量,且价值总和最大。
输出最大价值。
输入格式
第一行两个整数,N,VN,V,用空格隔开,分别表示物品种数和背包容积。
接下来有 NN 行,每行三个整数 vi,wi,sivi,wi,si,用空格隔开,分别表示第 ii 种物品的体积、价值和数量。
- si=−1si=−1 表示第 ii 种物品只能用1次;
- si=0si=0 表示第 ii 种物品可以用无限次;
- si>0si>0 表示第 ii 种物品可以使用 sisi 次;
输出格式
输出一个整数,表示最大价值。
数据范围
0<N,V≤10000<N,V≤1000
0<vi,wi≤10000<vi,wi≤1000
−1≤si≤1000−1≤si≤1000
输入样例
4 5
1 2 -1
2 4 1
3 4 0
4 5 2
输出样例:
8
题解
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 1e3 + 10;
int dp[maxn];
struct item { int c, w, s; };
vector<item> vec;
inline const int read()
{
int x = 0, f = 1; char ch = getchar();
while (ch < '0' || ch > '9') { if (ch == '-') f = -1; ch = getchar(); }
while (ch >= '0' && ch <= '9') { x = (x << 3) + (x << 1) + ch - '0'; ch = getchar(); }
return x * f;
}
int main()
{
int n = read(), m = read();
for (int i = 1; i <= n; i++)
{
int c = read(), w = read(), s = read();
if (s <= 0) vec.push_back(item{ c, w, s });
else
{
for (int j = 1; j <= s; j <<= 1)
{
s -= j;
vec.push_back(item{ c * j, w * j, -1 });
}
if (s > 0) vec.push_back(item{ c * s, w * s, -1 });
}
}
for (auto it : vec)
{
if (it.s == -1)
for (int i = m; i >= it.c; i--)
dp[i] = max(dp[i], dp[i - it.c] + it.w);
else
for (int i = it.c; i <= m; i++)
dp[i] = max(dp[i], dp[i - it.c] + it.w);
}
printf("%d\n", dp[m]);
return 0;
}
二维费用的背包问题
有 NN 件物品和一个容量是 VV 的背包,背包能承受的最大重量是 MM。
每件物品只能用一次。体积是 vivi,重量是 mimi,价值是 wiwi。
求解将哪些物品装入背包,可使物品总体积不超过背包容量,总重量不超过背包可承受的最大重量,且价值总和最大。
输出最大价值。
输入格式
第一行两个整数,N,V,MN,V,M,用空格隔开,分别表示物品件数、背包容积和背包可承受的最大重量。
接下来有 NN 行,每行三个整数 vi,mi,wivi,mi,wi,用空格隔开,分别表示第 ii 件物品的体积、重量和价值。
输出格式
输出一个整数,表示最大价值。
数据范围
0<N≤10000<N≤1000
0<V,M≤1000<V,M≤100
0<vi,mi≤1000<vi,mi≤100
0<wi≤10000<wi≤1000
输入样例
4 5 6
1 2 3
2 4 4
3 4 5
4 5 6
输出样例:
8
题解
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 1e3 + 10;
int n, v, m, dp[maxn][maxn];
struct item { int v, m, w; } items[maxn];
inline const int read()
{
int x = 0, f = 1; char ch = getchar();
while (ch < '0' || ch > '9') { if (ch == '-') f = -1; ch = getchar(); }
while (ch >= '0' && ch <= '9') { x = (x << 3) + (x << 1) + ch - '0'; ch = getchar(); }
return x * f;
}
int main()
{
int n = read(), v = read(), m = read();
for (int i = 1; i <= n; i++)
{
items[i].v = read();
items[i].m = read();
items[i].w = read();
}
for (int i = 1; i <= n; i++)
for (int j = v; j >= items[i].v; j--)
for (int k = m; k >= items[i].m; k--)
dp[j][k] = max(dp[j][k], dp[j - items[i].v][k - items[i].m] + items[i].w);
printf("%d\n", dp[v][m]);
return 0;
}
分组背包问题
有 NN 组物品和一个容量是 VV 的背包。
每组物品有若干个,同一组内的物品最多只能选一个。
每件物品的体积是 vijvij,价值是 wijwij,其中 ii 是组号,jj 是组内编号。
求解将哪些物品装入背包,可使物品总体积不超过背包容量,且总价值最大。
输出最大价值。
输入格式
第一行有两个整数 N,VN,V,用空格隔开,分别表示物品组数和背包容量。
接下来有 NN 组数据:
- 每组数据第一行有一个整数 SiSi,表示第 ii 个物品组的物品数量;
- 每组数据接下来有 SiSi 行,每行有两个整数 vij,wijvij,wij,用空格隔开,分别表示第 ii 个物品组的第 jj 个物品的体积和价值;
输出格式
输出一个整数,表示最大价值。
数据范围
0<N,V≤1000<N,V≤100
0<Si≤1000<Si≤100
0<vij,wij≤1000<vij,wij≤100
输入样例
3 5
2
1 2
2 4
1
3 4
1
4 5
输出样例:
8
题解
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 1e2 + 10;
int n, m, s[maxn], c[maxn][maxn], w[maxn][maxn], dp[maxn];
inline const int read()
{
int x = 0, f = 1; char ch = getchar();
while (ch < '0' || ch > '9') { if (ch == '-') f = -1; ch = getchar(); }
while (ch >= '0' && ch <= '9') { x = (x << 3) + (x << 1) + ch - '0'; ch = getchar(); }
return x * f;
}
int main()
{
int n = read(), m = read();
for (int i = 1; i <= n; i++)
{
s[i] = read();
for (int j = 1; j <= s[i]; j++)
{
c[i][j] = read();
w[i][j] = read();
}
}
for (int i = 1; i <= n; i++)
for (int j = m; j >= 0; j--)
for (int k = 1; k <= s[i]; k++)
if (c[i][k] <= j)
dp[j] = max(dp[j], dp[j - c[i][k]] + w[i][k]);
printf("%d\n", dp[m]);
return 0;
}
有依赖的背包问题
有 NN 个物品和一个容量是 VV 的背包。
物品之间具有依赖关系,且依赖关系组成一棵树的形状。如果选择一个物品,则必须选择它的父节点。
如下图所示:
如果选择物品5,则必须选择物品1和2。这是因为2是5的父节点,1是2的父节点。
每件物品的编号是 ii,体积是 vivi,价值是 wiwi,依赖的父节点编号是 pipi。物品的下标范围是 1…N1…N。
求解将哪些物品装入背包,可使物品总体积不超过背包容量,且总价值最大。
输出最大价值。
输入格式
第一行有两个整数 N,VN,V,用空格隔开,分别表示物品个数和背包容量。
接下来有 NN 行数据,每行数据表示一个物品。
第 ii 行有三个整数 vi,wi,pivi,wi,pi,用空格隔开,分别表示物品的体积、价值和依赖的物品编号。
如果 pi=−1pi=−1,表示根节点。 数据保证所有物品构成一棵树。
输出格式
输出一个整数,表示最大价值。
数据范围
1≤N,V≤1001≤N,V≤100
1≤vi,wi≤1001≤vi,wi≤100
父节点编号范围:
- 内部结点:1≤pi≤N1≤pi≤N;
- 根节点 pi=−1pi=−1;
输入样例
5 7
2 3 -1
2 2 1
3 5 1
4 7 2
3 6 2
输出样例:
11
题解
树形DP 有关泛化物品没弄明白,先mark改日再补
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 1e2 + 10;
int n, m, p, root, cnt, h[maxn], c[maxn], w[maxn], dp[maxn][maxn];
struct edge { int to, next; } e[maxn];
inline const int read()
{
int x = 0, f = 1; char ch = getchar();
while (ch < '0' || ch > '9') { if (ch == '-') f = -1; ch = getchar(); }
while (ch >= '0' && ch <= '9') { x = (x << 3) + (x << 1) + ch - '0'; ch = getchar(); }
return x * f;
}
void addEdge(int u, int v)
{
e[cnt].to = v;
e[cnt].next = h[u];
h[u] = cnt++;
}
void dfs(int u)
{
for (int i = h[u]; ~i; i = e[i].next)
{
int v = e[i].to;
dfs(v);
for (int j = m - c[u]; j >= 0; j--)
for (int k = 0; k <= j; k++)
dp[u][j] = max(dp[u][j], dp[u][j - k] + dp[v][k]);
}
for (int i = m; i >= c[u]; i--) dp[u][i] = dp[u][i - c[u]] + w[u];
for (int i = 0; i < c[u]; i++) dp[u][i] = 0;
}
int main()
{
memset(h, -1, sizeof(h));
n = read(); m = read();
for (int i = 1; i <= n; i++)
{
c[i] = read(); w[i] = read();
if ((p = read()) == -1) root = i;
else addEdge(p, i);
}
dfs(root);
printf("%d\n", dp[root][m]);
return 0;
}
背包问题求方案数
有 NN 件物品和一个容量是 VV 的背包。每件物品只能使用一次。
第 ii 件物品的体积是 vivi,价值是 wiwi。
求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。
输出 最优选法的方案数。注意答案可能很大,请输出答案模 109+7109+7 的结果。
输入格式
第一行两个整数,N,VN,V,用空格隔开,分别表示物品数量和背包容积。
接下来有 NN 行,每行两个整数 vi,wivi,wi,用空格隔开,分别表示第 ii 件物品的体积和价值。
输出格式
输出一个整数,表示 方案数 模 109+7109+7 的结果。
数据范围
0<N,V≤10000<N,V≤1000
0<vi,wi≤10000<vi,wi≤1000
输入样例
4 5
1 2
2 4
3 4
4 6
输出样例:
2
题解
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 1e3 + 10;
const int mod = 1e9 + 7;
int c[maxn], w[maxn], f[maxn], g[maxn];
inline const int read()
{
int x = 0, f = 1; char ch = getchar();
while (ch < '0' || ch > '9') { if (ch == '-') f = -1; ch = getchar(); }
while (ch >= '0' && ch <= '9') { x = (x << 3) + (x << 1) + ch - '0'; ch = getchar(); }
return x * f;
}
int main()
{
int n = read(), m = read();
for (int i = 0; i <= m; i++) g[i] = 1;
for (int i = 1; i <= n; i++)
{
c[i] = read();
w[i] = read();
}
for (int i = 1; i <= n; i++)
{
for (int j = m; j >= c[i]; j--)
{
if (f[j] < f[j - c[i]] + w[i])
{
g[j] = g[j - c[i]];
f[j] = f[j - c[i]] + w[i];
}
else if (f[j] == f[j - c[i]] + w[i]) g[j] = (g[j] + g[j - c[i]]) % mod;
}
}
printf("%d\n", g[m]);
return 0;
}