Loading

[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}\)

\[g_{b, w} = \max_k f_{b, w - k} + g_{b - 1, 2k + M_{b - 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\)

posted @ 2024-12-13 19:35  Yorg  阅读(5)  评论(0编辑  收藏  举报