遗迹探险家小红
题目分析
小红在遗迹中收集宝物,共 件宝物,每件宝物有耗时
和价值
。限制条件:
- 总探索时间不超过
分钟
- 耗时超过 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 数组。

京公网安备 11010502036488号