完全背包:dp[t] 表示限时 t 的最大得分,容量正序使种类可重复选取。
OJ: roj
题目 ID: 2041
难度:普及-
标签:动态规划背包完全背包python
创建: 2026-10-01 04:43
更新: 2026-10-07 13:50
前置题目
roj 1294Charm BraceletA 教的一维背包 f[v] 状态与转移被 B 完整沿用,B 只把 A 的倒序改成容量维正序,从而由 0-1 选取变为可重复选取的完全背包。luogu U661986疯狂的背包问题(1) - 01背包问题B 的完全背包完全沿用 A 教的一维转移式 dp[t]=max(dp[t],dp[t-t_i]+p_i) 与「扫描方向决定件数」这一步,只是把 A 的容量倒序(每件只选一次)换成正序(种类可重复选),并在此之上叠加按耗时升序处理与被支配种类剪枝。luogu U661988疯狂的背包问题(4) - 完全背包问题A 教的容量正序枚举=物品可重复选,正是 B 完全背包解法中 range(minutes, limit+1) 这一步的直接复用,B 只是在其上叠加被支配种类剪枝
题目描述
给定竞赛限时
输入格式:第一行
样例:
思路
令
参考代码
Python 版:
python
#!/usr/bin/env python3
# Author by Rainboy blog: https://rainboylv.com github: https://github.com/rainboylvx
# rbook: -> https://rbook.roj.ac.cn https://rbook2.roj.ac.cn
# rainboy的学习导航网站: https://idx.roj.ac.cn
# create_at: 2026-10-01 04:45
# update_at: 2026-10-01 04:45
import sys
def best_total(limit: int, types: list[tuple[int, int]]) -> int:
"""在限时 limit 内,从这些 (耗时, 得分) 种类里任选题目,最多能拿多少分。"""
# dp[t] = 限时 t 内的最大得分。容量维正序遍历,dp[t - w] 已含本轮结果,
# 同一种类因此能被反复选取;若每种至多选一题,这里必须改成倒序。
dp = [0] * (limit + 1)
for minutes, points in types: # types 已按耗时升序
if minutes > limit:
break # 耗时升序,后面的种类耗时更长,连一题都放不下
if dp[minutes] >= points:
# 前面(耗时不超过 minutes)的种类已经能用不超过 minutes 的时间拿到不少于 points
# 的分:把最优解里每道这种题目都换成那一份,耗时不增、得分不降,所以这种类
# 不会出现在任何最优解里,直接剪掉;同耗时的种类也因此只留下得分最高的那个。
continue
for t in range(minutes, limit + 1):
candidate = dp[t - minutes] + points # 至少再选一道这种题目
if candidate > dp[t]:
dp[t] = candidate
return dp[limit]
def solve() -> None:
data = iter(map(int, sys.stdin.buffer.read().split()))
limit, count = next(data), next(data) # 竞赛限时 M 与题目"种类"数 N
tokens = [next(data) for _ in range(2 * count)] # 每个种类两个数:得分在前、耗时在后
# 重排成 (耗时, 得分) 并按耗时升序;耗时相同时得分高的排前面,低分的会被剪掉。
types = sorted(zip(tokens[1::2], tokens[0::2]), key=lambda kind: (kind[0], -kind[1]))
print(best_total(limit, types))
if __name__ == "__main__":
solve()C++ 版(同一算法):
cpp
/**
* Author by Rainboy blog: https://rainboylv.com github: https://github.com/rainboylvx
* rbook: -> https://rbook.roj.ac.cn https://rbook2.roj.ac.cn
* rainboy的学习导航网站: https://idx.roj.ac.cn
* create_at: 2026-10-06 10:09
* update_at: 2026-10-06 10:09
*/
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long ll;
const int MAXM = 10000;
int M, N;
ll dp[MAXM + 1]; // dp[t] 表示限时 t 内的最大得分
struct Kind {
int t; // 耗时
int p; // 得分
} kinds[MAXM + 1];
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin >> M >> N;
for (int i = 1; i <= N; ++i) {
int points, minutes;
cin >> points >> minutes;
kinds[i].t = minutes;
kinds[i].p = points;
}
// 按耗时升序排序,耗时相同则得分高的在前
for (int i = 1; i <= N; ++i) {
for (int j = i + 1; j <= N; ++j) {
if (kinds[j].t < kinds[i].t ||
(kinds[j].t == kinds[i].t && kinds[j].p > kinds[i].p)) {
Kind tmp = kinds[i];
kinds[i] = kinds[j];
kinds[j] = tmp;
}
}
}
for (int i = 1; i <= N; ++i) {
int t = kinds[i].t;
int p = kinds[i].p;
if (t > M) break; // 耗时升序,后面的种类耗时更长,连一题都放不下
if (dp[t] >= p) continue; // 被支配种类,跳过
// 完全背包:容量正序遍历,同一种类可重复选取
for (int cap = t; cap <= M; ++cap) {
ll cand = dp[cap - t] + p;
if (cand > dp[cap]) dp[cap] = cand;
}
}
cout << dp[M] << "\n";
return 0;
}