遗迹探险家小红

题目分析

小红在遗迹中收集宝物,共 件宝物,每件宝物有耗时 和价值 。限制条件:

  • 总探索时间不超过 分钟
  • 耗时超过 30 分钟的"沉重宝物"最多选
  • 每件宝物最多选一次

求能获得的最大总价值。

本题是带额外维度约束的 0/1 背包问题。

思路

二维费用背包

经典 0/1 背包只有"容量"一个维度的约束。本题多了一个约束:沉重宝物()的数量不能超过 。因此需要在状态中多记录一个维度。

定义 为"使用时间恰好为 ,且选了 件沉重宝物时的最大价值"。

对于每件宝物 ,判断它是否为沉重宝物(),转移方程为:

$$

按照 0/1 背包的套路, 都需要逆序遍历,以避免同一件物品被重复选取。

最终答案为 (注意:由于转移中 不会为负,轻宝物在所有 层都做了更新,所以 包含了"选 件沉重宝物"的最优方案)。

示例推演

输入 4 100 1,四件宝物:

  • 宝物 1(, 轻):更新所有
  • 宝物 2(, 重):只更新
  • 宝物 3(, 轻):更新所有
  • 宝物 4(, 重):只更新

最终 ,对应选宝物 1、3、4(时间 ,沉重宝物 1 件)。

代码

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

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

    int n, T, m;
    cin >> n >> T >> m;

    // dp[j][k] = 使用时间 j、选 k 件沉重宝物时的最大价值
    vector<vector<int>> dp(T + 1, vector<int>(m + 1, 0));

    for(int i = 0; i < n; i++){
        int t, v;
        cin >> t >> v;
        int heavy = (t > 30) ? 1 : 0;
        for(int j = T; j >= t; j--){
            for(int k = m; k >= heavy; k--){
                dp[j][k] = max(dp[j][k], dp[j - t][k - heavy] + v);
            }
        }
    }

    cout << dp[T][m] << endl;
    return 0;
}
import java.util.Scanner;

public class Main {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int n = sc.nextInt(), T = sc.nextInt(), m = sc.nextInt();

        int[][] dp = new int[T + 1][m + 1];

        for (int i = 0; i < n; i++) {
            int t = sc.nextInt(), v = sc.nextInt();
            int heavy = t > 30 ? 1 : 0;
            for (int j = T; j >= t; j--) {
                for (int k = m; k >= heavy; k--) {
                    dp[j][k] = Math.max(dp[j][k], dp[j - t][k - heavy] + v);
                }
            }
        }

        System.out.println(dp[T][m]);
    }
}
import sys

def main():
    data = sys.stdin.buffer.read().split()
    ptr = 0
    n = int(data[ptr]); ptr += 1
    T = int(data[ptr]); ptr += 1
    m = int(data[ptr]); ptr += 1

    heavy = []
    light = []
    for _ in range(n):
        ti = int(data[ptr]); ptr += 1
        vi = int(data[ptr]); ptr += 1
        if ti > 30:
            heavy.append((ti, vi))
        else:
            light.append((ti, vi))

    m = min(m, len(heavy))
    nh = len(heavy)

    # 轻宝物:标准 0/1 背包
    sum_light = sum(ti for ti, vi in light)
    TL = min(T, sum_light)
    light_dp = [0] * (TL + 1)
    for ti, vi in light:
        for j in range(TL, ti - 1, -1):
            v = light_dp[j - ti] + vi
            if v > light_dp[j]:
                light_dp[j] = v

    # 前缀最大值:light_dp[j] = 用时 ≤ j 的最大价值
    for j in range(1, TL + 1):
        if light_dp[j] < light_dp[j-1]:
            light_dp[j] = light_dp[j-1]

    # 沉重宝物:二维背包 [数量][时间],按时间排序+上界剪枝
    heavy.sort()
    sum_heavy = sum(ti for ti, vi in heavy)
    TH = min(T, sum_heavy)
    heavy_dp = [[0] * (TH + 1) for _ in range(m + 1)]
    max_reach = [0] * (m + 1)

    for idx in range(nh):
        ti, vi = heavy[idx]
        max_k = min(idx + 1, m)
        for k in range(max_k, 0, -1):
            hk = heavy_dp[k]
            hk1 = heavy_dp[k - 1]
            upper = min(max_reach[k-1] + ti, TH)
            for j in range(upper, ti - 1, -1):
                v = hk1[j - ti] + vi
                if v > hk[j]:
                    hk[j] = v
            if upper > max_reach[k]:
                max_reach[k] = upper

    # 合并轻型和沉重宝物
    ans = light_dp[min(T, TL)]
    for k in range(1, m + 1):
        hk = heavy_dp[k]
        for j in range(min(max_reach[k], TH) + 1):
            v = hk[j]
            if v > 0:
                remain = T - j
                lv = light_dp[min(remain, TL)] if remain >= 0 else 0
                total = v + lv
                if total > ans:
                    ans = total

    sys.stdout.write(str(ans) + '\n')

main()
const readline = require('readline');
const rl = readline.createInterface({ input: process.stdin });
const lines = [];
rl.on('line', l => lines.push(l.trim()));
rl.on('close', () => {
    const [n, T, m] = lines[0].split(' ').map(Number);
    // dp[j][k] = 使用时间 j、选 k 件沉重宝物时的最大价值
    const dp = Array.from({length: T + 1}, () => new Int32Array(m + 1));

    for (let i = 0; i < n; i++) {
        const [t, v] = lines[i + 1].split(' ').map(Number);
        const heavy = t > 30 ? 1 : 0;
        for (let j = T; j >= t; j--) {
            for (let k = m; k >= heavy; k--) {
                dp[j][k] = Math.max(dp[j][k], dp[j - t][k - heavy] + v);
            }
        }
    }

    console.log(dp[T][m]);
});

复杂度分析

  • 时间复杂度,三重循环遍历物品、时间和沉重数量。Python 版本将物品分为轻/重两组分别做背包再合并,配合上界剪枝降低常数。
  • 空间复杂度,二维 DP 数组。