前言

因为我的思维水平有点跟不上我的科技水平了,所以来练一练我此前几乎没有开过的板块:动态规划,背包问题作为动态规划的基础,自然是得好好学学的。

多重背包

引入

你有一个容量为 $V$ 的背包,以及 $n$ 种物品。第 $i$ 种物品的体积为 $w_i$,每件价值为 $val_i$,最多有 $c_i$ 件。

你可以选择每种物品若干件放入背包,但同一种物品选择的件数不能超过 $c_i$,且总容量不能超过 $V$。

请你求出在不超过背包容量的前提下,能获得的最大总价值。

思路

我们可以把每件物品,建模为 01 背包问题中的 $c_i$ 件物品,这样就可以进行简单的转移了,但是这样太慢了,我们需要考虑一些性质进行优化。

二进制拆分定理:任意正整数都可以唯一地表示成若干个互不相同的 2 的幂次之和。

我们根据这个定理,就可以像倍增法那样,把数量为 $s$ 的物品按 $1,2,4,8,…$ 进行分组,剩余部分单独成组,就可以表示所有的数了,时间复杂度可以进一步下降到 $O(V·Σlog s)$。

1
2
3
4
5
6
7
8
9
10
11
12
for(int i=1;i<=n;i++) {
int p=c[i];
for(int k=1;p>0;k*=2) {
int b=min(k,p);
p-=b;
int W=b*w[i];
int V=b*val[i];
for(int j=v;j>=W;j--) {
dp[j]=max(dp[j],dp[j-W]+V);
}
}
}

混合背包

引入

混合背包就是将前面三种的背包问题混合起来,有的只能取一次,有的能取无限次,有的只能取 $k$ 次。

思路

这种题目看起来很吓人,可是只要领悟了前面几种背包的中心思想,并将其合并在一起就可以了,我们这里结合 一道例题 进行讲解:

有 (n) 种樱花树和长度为 (T) 的时间,有的樱花树只能看一遍,有的樱花树最多看 (A_i) 遍,有的樱花树可以看无数遍。每棵樱花树都有一个美学值 (C_i),求在 (T) 的时间内看哪些樱花树能使美学值最高。

下面给出解决此类问题的伪代码:

1
2
3
4
5
6
7
8
for (循环物品种类) {
if (是 0 - 1 背包)
套用 0 - 1 背包代码;
else if (是完全背包)
套用完全背包代码;
else if (是多重背包)
套用多重背包代码;
}

对应到这道题来说,也是同理,根据能看的次数就可以判定了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#include<bits/stdc++.h>
using namespace std;

int dp[1005];

int main() {
int h1, m1, h2, m2, n;
scanf("%d:%d %d:%d %d", &h1, &m1, &h2, &m2, &n);

int m = (h2 - h1) * 60 + m2 - m1;

for (int i = 1; i <= n; i++) {
int t, c, p;
scanf("%d%d%d", &t, &c, &p);

if (p == 0) {
for (int j = t; j <= m; j++)
dp[j] = max(dp[j], dp[j - t] + c);
} else {
for (int k = 1; p > 0; k *= 2) {
int x = min(k, p);
p -= x;

int w = x * t;
int v = x * c;
for (int j = m; j >= w; j--)
dp[j] = max(dp[j], dp[j - w] + v);
}
}
}

printf("%d\n", dp[m]);
return 0;
}