找啊找啊找GF
用二维 01 背包同时记录最多能泡到的 MM 数和对应的最少时间。
OJ: luogu
题目 ID: P1509
难度:普及/提高-
标签:动态规划01背包背包
日期: 2026-06-19 16:22
题意
有 n 个 MM,每个 MM 有三个属性:
- 需要花的钱
rmb_i - 需要花的人品
rp_i - 搞定她需要的时间
time_i
sqybi 只有 m 块大洋和 r 点人品。
他想在不超过这两个限制的前提下,尽量多泡到 MM;
如果能泡到的数量一样多,就希望总时间更少。
这张表把题意翻成了背包模型:
| 原题对象 | 背包含义 |
|---|---|
| 一个 MM | 一个 0/1 物品 |
| 钱和人品 | 双重容量 |
| 泡到的数量 | 第一优先级 |
| 总时间 | 第二优先级 |
思路
一句话本质:二维费用的 01 背包,状态是
先看最直接的暴力:
py
import sys
data = list(map(int, sys.stdin.buffer.read().split()))
n = data[0]
mms = []
idx = 1
for _ in range(n):
rmb, rp, time = data[idx], data[idx + 1], data[idx + 2]
idx += 3
mms.append((rmb, rp, time))
m, r = data[idx], data[idx + 1]
best_cnt = 0
best_time = 0
for mask in range(1 << n):
cnt = 0
tot_rmb = 0
tot_rp = 0
tot_time = 0
for i in range(n):
if mask >> i & 1:
rmb, rp, time = mms[i]
tot_rmb += rmb
tot_rp += rp
tot_time += time
cnt += 1
if tot_rmb <= m and tot_rp <= r:
if cnt > best_cnt or (cnt == best_cnt and tot_time < best_time):
best_cnt = cnt
best_time = tot_time
print(best_time)brute.py 枚举
为什么一个人就对应一个 0/1 物品,却需要二维背包?
普通 01 背包只有一个容量(比如总钱数)。但这题有两个独立限制:钱
两个目标(数量和时间)怎么比较?
先最大化 MM 数量,数量相同时最小化时间。这和普通 max 不同——不能只用一个大整数比较。需要把状态写成一个带优先级的 pair:
转移时怎么判断"更优"?
设当前 MM 花费
如果
代码
cpp
#include <bits/stdc++.h>
using namespace std;
const int MAXR = 105;
struct State {
int cnt;
int time;
State() : cnt(0), time(0) {}
State(int c, int t) : cnt(c), time(t) {}
bool better_than(const State& o) const {
if (cnt != o.cnt) return cnt > o.cnt;
return time < o.time;
}
};
int n, m, r;
int rmb[105], rp[105], tmm[105];
// dp[a][b] 表示花 a 元 RMB、b 点 RP 时,能泡到的最多 MM 数及最少时间。
State dp[MAXR][MAXR];
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin >> n;
for (int i = 1; i <= n; i++) {
cin >> rmb[i] >> rp[i] >> tmm[i]; // 花费 RMB、RP,所需时间
}
cin >> m >> r; // 总 RMB 和总 RP
// 0/1 背包:二维费用,倒序枚举两维。
for (int i = 1; i <= n; i++) {
for (int a = m; a >= rmb[i]; a--) {
for (int b = r; b >= rp[i]; b--) {
State nxt(dp[a - rmb[i]][b - rp[i]].cnt + 1,
dp[a - rmb[i]][b - rp[i]].time + tmm[i]);
if (nxt.better_than(dp[a][b])) {
dp[a][b] = nxt;
}
}
}
}
cout << dp[m][r].time << '\n';
return 0;
}复杂度
- 时间复杂度:
- 空间复杂度:
总结
这题本质上是二维 0/1 背包,只是比较方式不是单纯的“最大值”:
- 先比能选到的 MM 数量
- 再比总时间
遇到这种“先最大化数量,再最小化代价”的题,就可以把状态写成一个带优先级的 pair,然后做标准倒序 0/1 背包。
一图流解析
这张图把本题的建模、关键转移、实现检查和训练方法压缩到一页,适合读完正文后复盘。
