[HNOI2007] 梦幻岛宝珠
思路
听 \(\rm{ZCY}\) 大佬讲的, 有些困难, 又去看了 \(\rm{TJ}\)
首先题目中有很明确的提示, 即任意一个物品的价值可以表示为 \(a \times 2^b\)
我们将物品按照 \(b\) 来分组, 令 \(f_{b, W}\) 表示对于所有 \(w_i = a \times 2^b\) 这样的物品, 使用了 \(W\) 的背包容量
我们注意到对于 \(b\) 位的容量, 即使把所有的 \(n\) 都丢过来, 也不会超过 \(W_{max} = 10 \times n \approx 1000\)
那么这个柿子显然是开的下的 (其实这个题另一大提示就是 \(n \leq 100\))
那么同一般的背包问题, 有 \(f_{b, j} = \max f_{b, j - v_i} + w_i\)
但是我们还有很多个组, 怎么合并答案呢?
我们考虑同一般的合并答案, 令 \(g_{b, W}\) 表示对于 \(0 \sim b\) 的组, 使用了 \(W\) 的容积, 最大的价值
考虑转移, 显然的, 对于 \(W = (10100101)_2\) 这种情况, 我们使用完第 \(i\) 位的 \(k\) 个 \(1\), 之后的位的最大背包容量即为 \(2k \cdot 2^{i - 1}\)
其中 \(M_i\) 表示总背包容量的二进制中第 \(i\) 位的值
根据定义, 答案应当是 \(g_{len, 1}\) , 其中 \(len\) 为 \(W\) 中最高位 \(1\) 的位置
实现
框架
读入之后计算每个物品的 \(a, b\) , 然后计算 \(f\) , 在统计 \(g\)
代码
#include <bits/stdc++.h>
const int MAXN = 520;
const int MAXVAL = 50000;
#define int long long
int n, W; // 物品数量和背包容量
struct Bead_str {
int v; // 价值
int w; // 容积
int a, b; // 系数
} Beads[MAXN];
std::vector<int> B[50];
int f[50][MAXVAL], g[50][MAXVAL];
int C; // 最大的容积
/*计算 f*/
void fcalc()
{
for (int k = 0; k <= 30; k++) {
if (!B[k].size()) continue;
for (int i = 0; i < B[k].size(); i++) {
int now = B[k][i];
for (int j = C; j >= Beads[now].a; j--) {
f[k][j] = std::max(f[k][j], f[k][j - Beads[now].a] + Beads[now].v);
}
}
}
}
/*计算 g*/
void gcalc()
{
int Ans = 0;
for (int j = C; j >= 0; j--) {
g[0][j] = f[0][j];
}
for (int k = 1; k <= 30; k++) {
for (int j = C; j >= 0; j--) {
for (int i = 0; i <= j; i++) {
g[k][j] = std::max(g[k][j], f[k][j - i] + g[k - 1][2 * i + ((W >> (k - 1)) & 1)]);
}
}
}
int len = 0; while (W >> len) len++; len--;
printf("%lld\n", g[len][1]);
}
signed main()
{
while (~scanf("%lld %lld", &n, &W) && ~n && ~W) {
C = 0;
memset(f, 0, sizeof(f)); memset(g, 0, sizeof(g)); memset(B, 0, sizeof(B));
for (int i = 1; i <= n; i++) {
scanf("%lld %lld", &Beads[i].w, &Beads[i].v);
for (int j = 30; j >= 0; j--)
if (!(Beads[i].w % (1 << j))) {Beads[i].a = Beads[i].w / (1 << j), Beads[i].b = j; break;} // 计算 a, b`
B[Beads[i].b].push_back(i);
C += Beads[i].a;
}
fcalc();
gcalc();
}
return 0;
}
总结
二进制情况下, 从高位向低位枚举可以解决一类问题
分组类型的问题考虑新开一个数组合并
这道题相当于把开不下, 算不了的 \(W\) , 转化到了二进制的每一位上分组处理
本题中的容量上限 \(C\) 不清楚, 考虑最劣情况 \(C = \sum a\)