用完全背包求每个面值的最少邮票数,再扫描最长连续可达前缀。
OJ: luogu
题目 ID: P2725
难度:普及-
标签:动态规划完全背包背包
日期: 2026-06-19 16:59
题意
有
但是一封信最多只能贴 1 到
这张表把题意翻成了背包模型:
| 原题对象 | 背包含义 |
|---|---|
| 一种邮票面值 | 一个可以无限使用的物品 |
| 使用一张邮票 | 物品重量增加对应面值 |
| 最多 |
背包里的“张数”上限 |
| 最长连续可达前缀 | 目标答案 |
思路
先看最直接的暴力:
cpp
#include <bits/stdc++.h>
using namespace std;
int k, n;
vector<int> a;
set<int> reachable;
// 暴力枚举最多使用 k 枚邮票时能拼出的所有面值。
// 这个做法只适合小数据,但最容易直接对应题意。
void dfs(int used, int sum) {
if (used > k) {
return;
}
reachable.insert(sum);
if (used == k) {
return;
}
for (int v : a) {
dfs(used + 1, sum + v);
}
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin >> k >> n;
a.resize(n);
for (int i = 0; i < n; i++) {
cin >> a[i];
}
dfs(0, 0);
int answer = 0;
while (reachable.count(answer + 1)) {
answer++;
}
cout << answer << '\n';
return 0;
}brute.cpp 直接枚举最多
这个做法正确,但复杂度很高,只适合小数据验证。
关键观察是:
- 每种邮票可以无限次使用,所以是完全背包。
- 我们不需要知道“有多少种拼法”,只需要知道“最少要多少张邮票”。
- 一旦某个面值需要的邮票数超过
,最长连续前缀就断掉了。
于是设:
表示拼出面值 需要的最少邮票数
这张表说明状态定义:
| 状态 | 含义 |
|---|---|
| 凑出面值 |
处理每种邮票面值
这是标准的完全背包。
DP 公式
设
对每种邮票面值
最终从
公式解释:状态值是凑出某个面值所需的最少邮票数。完全背包转移允许同一种邮票多次使用;从小到大找第一个超过
代码
cpp
#include <bits/stdc++.h>
using namespace std;
const int INF = 1000000000;
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int k, n;
cin >> k >> n;
vector<int> a(n);
int max_a = 0;
for (int i = 0; i < n; i++) {
cin >> a[i];
max_a = max(max_a, a[i]);
}
int limit = k * max_a;
vector<int> dp(limit + 1, INF);
dp[0] = 0;
// 完全背包:每种面值的邮票可以使用无限次。
for (int v : a) {
for (int x = v; x <= limit; x++) {
dp[x] = min(dp[x], dp[x - v] + 1);
}
}
int answer = 0;
for (int x = 1; x <= limit; x++) {
if (dp[x] <= k) {
answer = x;
} else {
break;
}
}
cout << answer << '\n';
return 0;
}复杂度
- 时间复杂度:
- 空间复杂度:
总结
这题的核心不是“能不能拼出来”,而是“拼出它最少要几张邮票”。
当某个面值的最少张数第一次超过
一图流解析
这张图把本题的建模、关键转移、实现检查和训练方法压缩到一页,适合读完正文后复盘。
