[USACO07DEC] Charm Bracelet S

GitHub跳转原题关系图返回列表

把每个物品看成只能选一次的背包物品,按容量做一维 0/1 背包,维护不超过 M 时的最大价值。

OJ: luogu

题目 ID: P2871

难度:普及-

标签:动态规划01背包背包

创建: 2026-06-19 15:32

更新: 2026-10-07 10:45

后置题目
luogu P2347[NOIP 1996 提高组] 砝码称重B 的 main-rainboy.cpp 分支把 A 教的「每件物品只能选一次,所以容量必须倒序枚举」原样套到逐枚展开的砝码上(for j = sum..w[i]),只把 max 值转移改成布尔可达 dp[j] |= dp[j-w[i]],并在此基础上新增逐枚展开与可达重量计数。luogu P1734最大约数和B 的主解把 A 教的「内层容量倒序枚举、每件物品至多选一次」原样搬进 main.cpp(for(j=s;j>=i;--j) dp[j]=max(dp[j],dp[j-i]+val[i]) 与 A 的 dp[j]=max(dp[j],dp[j-W[i]]+D[i]) 同形),只把重量价值换成数字与真约数和、并在前面补上真约数和预处理这一额外台阶。luogu P1510精卫填海B 的 main.cpp 沿用 A 教的「容量倒序枚举保证每件物品最多选一次」这一步,把容量换成体力做同形转移后,再补上截断与扫描最小花费的新流程luogu P1910L 国的战斗之间谍B 直接复用 A 教的 0/1 背包“每个对象最多选一次 ⇒ 容量必须倒序枚举”这一步,只是把单个重量容量扩成探查风险与工资两维费用(main.cpp:26-30 两层倒序循环),模板级台阶成立。luogu P1855榨取kkksc03A 教的「容量倒序枚举保证物品只选一次」正是 B 把 dp[j] 扩成 dp[j][k] 时对两维容量都倒序枚举的那一步,转移仍是 dp=max(dp, dp[减去消耗]+价值),只多加一维费用、把价值固定为 1。luogu P2663越越的组队B 的 main.cpp 把 A 教的「容量倒序枚举保证每件物品只选一次」逐字搬进 dp[j][s] 二维转移(j 与 s 双重倒序、由 dp[j-1][s-a[i]] 推 dp[j][s]),额外台阶只是多了「恰好选 n/2 人」的人数维与最后从 sum/2 倒扫可达值。luogu P1507NASA的食物计划B 的二维费用 01 背包复用了 A 的“倒序枚举容量保证每件物品只选一次”这一具体步骤,只是在一维容量基础上叠加上体积、质量两维循环

题意

有 N 件物品和一个容量为 M 的背包。

  • 第 i 件物品有重量 W[i]
  • 第 i 件物品有价值 D[i]
  • 每件物品最多只能选一次

要求在总重量不超过 M 的前提下,让总价值最大。

这张表把题目直接翻译成了背包模型:

原题对象 背包含义
一件物品 一个只能选一次的物品
重量 W[i] 物品重量
价值 D[i] 物品价值
背包容量 M 背包容量

从表里可以看到,本题就是最标准的一维 0/1 背包。

思路

先看最直接的暴力:

cpp
#include <bits/stdc++.h>
using namespace std;

// brute.cpp:小数据暴力解,使用 01 序列枚举每个物品选或不选。

const int MAXN = 3405;

int n, m;
int weight[MAXN];
int value[MAXN];
int choose_item[MAXN]; // choose_item[i] = 0/1,表示第 i 个物品不选/选
int answer;

bool check() {
    int used_weight = 0;
    for (int i = 1; i <= n; i++) {
        if (choose_item[i] == 1) used_weight += weight[i];
    }
    return used_weight <= m;
}

int calc_answer() {
    int total_value = 0;
    for (int i = 1; i <= n; i++) {
        if (choose_item[i] == 1) total_value += value[i];
    }
    return total_value;
}

// dfs_choose 只负责生成完整 01 序列,合法性和答案统计放到叶子节点。
void dfs_choose(int dep) {
    if (dep == n + 1) {
        if (check()) {
            int current_value = calc_answer();
            if (answer < current_value) answer = current_value;
        }
        return;
    }

    for (int i = 0; i <= 1; i++) {
        choose_item[dep] = i;
        dfs_choose(dep + 1);
    }
}

void read_input() {
    cin >> n >> m;
    for (int i = 1; i <= n; i++) {
        cin >> weight[i] >> value[i];
    }
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    read_input();
    dfs_choose(1);
    cout << answer << '\n';

    return 0;
}

brute.cpp 把每个物品看成一个 01 选择:choose_item[i] = 0/1 表示不选或选。递归先生成完整选择,叶子节点再检查总重量是否超过 M,并统计奖励。

这个做法显然正确,但复杂度是 O(2N)O(2^N),只能做小数据验证。

关键观察是:每个物品只有“选 / 不选”两种决策,和 0/1 背包完全一致。

所以设:

  • dp[j] 表示容量不超过 j 时能获得的最大奖励

这张表说明状态定义:

状态 含义
dp[j] 容量不超过 j 时能获得的最大奖励

处理第 i 件物品 (W[i], D[i]) 时:

  • 不选它:dp[j] 保持原值
  • 选它:从 dp[j - W[i]] 转移过来,再加上 D[i]

于是转移就是:

  • dp[j] = max(dp[j], dp[j - W[i]] + D[i])

因为每件物品只能选一次,所以容量必须倒序枚举。

最后输出 dp[M] 即可。

DP 公式

设 dpjdp_j 表示容量不超过 jj 时能获得的最大奖励。处理重量 WiW_i、价值 DiD_i 的物品时:

dpj=max⁡(dpj, dpj−Wi+Di) dp_j=\max(dp_j,\ dp_{j-W_i}+D_i)

其中 j⩾Wij\geqslant W_i,容量倒序枚举。最终答案为:

dpM dp_M

公式解释:这是最标准的 0/1 背包。dp_j 表示容量 j 内的最大价值,选当前物品时消耗重量并增加奖励。

代码

cpp
#include <bits/stdc++.h>
using namespace std;

const int MAXN = 3405;
const int MAXM = 12885;

int n, m;
int weight[MAXN];
int value[MAXN];
int dp[MAXM]; // dp[j] 表示容量不超过 j 时能获得的最大奖励

void read_input() {
    cin >> n >> m;
    for (int i = 1; i <= n; i++) {
        cin >> weight[i] >> value[i];
    }
}

void solve() {
    for (int i = 1; i <= n; i++) {
        // 每个物品只能选一次,所以容量必须倒序枚举。
        for (int j = m; j >= weight[i]; j--) {
            dp[j] = max(dp[j], dp[j - weight[i]] + value[i]);
        }
    }

    cout << dp[m] << '\n';
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    read_input();
    solve();

    return 0;
}

复杂度

  • 时间复杂度:O(NM)O(NM)
  • 空间复杂度:O(M)O(M)

总结

这题是 0/1 背包最标准的模板题之一:

  • 每个物品最多选一次
  • 只有一个容量限制
  • 目标是最大化总价值

以后看到这类题,就可以直接往一维 0/1 背包上想。

一图流解析

这张图把本题的建模、关键转移、实现检查和训练方法压缩到一页,适合读完正文后复盘。

一图流解析