题解:洛谷 P2725 邮票
【题目来源】
【题目描述】
给一组 \(n\) 枚邮票的面值集合和一个上限 \(k\) —— 表示信封上能够贴 \(k\) 张邮票。请求出最大的正整数 \(m\),满足 \(1\) 到 \(m\) 的面值都可以用不超过 \(k\) 张邮票表示出来。
【输入】
输入的第一行是两个整数,分别代表邮票上限 \(k\) 和邮票面值数 \(n\)。
自第二行起,除最后一行外,每行有 \(15\) 个整数 \(a_i\) ,最后一行的整数个数不超过 \(15\),共有 \(n\) 个整数,第 \(i\) 个整数代表第 \(i\) 种邮票的面值 \(a_i\)。
【输出】
输出一行一个整数代表 \(m\)。若 \(m\) 不存在请输出 \(0\)。
【输入样例】
5 2
1 3
【输出样例】
13
【核心思想】
-
问题分析:给定 \(n\) 种邮票面值(每种无限张)和最多贴 \(k\) 张的限制,求最大的正整数 \(m\),使得 \(1\) 到 \(m\) 的每个面值都能用不超过 \(k\) 张邮票表示。这是一个完全背包的变形问题,核心在于用 DP 计算每个面值所需的最少邮票张数,再找到最长的连续可达前缀。
-
算法选择:
- 完全背包:\(f[j]\) 表示凑出面值 \(j\) 所需的最少邮票张数
- 正序遍历:每种邮票可无限使用,内层循环正序枚举面值
- 连续判定:从 \(0\) 开始遍历,找到最后一个满足 \(f[j] \leq k\) 的连续面值
-
关键步骤:
- 初始化:读取 \(k\)(最多张数)、\(n\)(种类数)、\(a[1..n]\)(面值)
- DP 初始化:\(f[0] = 0\),其余 \(f[j] = \infty\)
- 完全背包递推(\(i\) 从 \(1\) 到 \(n\),\(j\) 从 \(a_i\) 到 \(N-1\)):
- \(f[j] = \min(f[j], f[j - a_i] + 1)\)(不用第 \(i\) 种或用一张第 \(i\) 种)
- 找最大连续 \(m\):遍历 \(j\) 从 \(0\) 开始:
- 若 \(f[j] \leq k\):\(ans = j\)(该面值可达)
- 否则:\(break\)(连续性中断,\(1\) 到 \(m\) 必须连续)
- 输出答案 \(ans\)
-
时间/空间复杂度:
- 时间复杂度:\(O(n \times N)\),其中 \(N\) 为 DP 数组上限(需覆盖可能的 \(m\) 值)
- 空间复杂度:\(O(N)\),一维 \(f\) 数组
-
完全背包的核心思想:
- 无限物品模型:每种邮票可无限使用,正序遍历 \(j\) 保证同一物品可被多次选取
- 最少张数状态:\(f[j]\) 记录凑出 \(j\) 的最小张数,而非最大价值,状态转移取 \(\min\)
- 连续性判定关键:题目要求 \(1\) 到 \(m\) 全部可达,因此一旦出现 \(f[j] > k\) 或 \(f[j] = \infty\) 必须立即终止,不能跳过
- 边界处理:\(f[0] = 0\) 是递推基础,所有面值的凑出都依赖于更小的面值
- 适用于无限资源限制、最少/最多数量优化、连续覆盖判定类问题
【算法标签】
普及 #完全背包
【代码详解】
#include <bits/stdc++.h>
using namespace std;
const int N = 200*10000+5; // DP数组上限:最大可能的面值范围
int n, k, a[55], ans; // k:最多贴k张邮票, n:邮票种类数, a[i]:第i种邮票的面值, ans:答案m
int f[N]; // f[i]:凑出面值i所需的最少邮票张数(完全背包)
int main()
{
cin >> k >> n; // 读入邮票上限k和邮票种类数n
for (int i=1; i<=n; i++) // 读入每种邮票的面值
cin >> a[i];
// 初始化:将所有面值的最小张数设为无穷大
memset(f, 0x3f, sizeof(f));
f[0] = 0; // 边界条件:凑出面值0需要0张邮票
// 完全背包:每种邮票可以使用无限次
for (int i=1; i<=n; i++) // 外层循环:枚举每种邮票
for (int j=a[i]; j<N; j++) // 内层循环:正序枚举面值(完全背包允许重复使用)
{
// 状态转移:凑出面值j的最小张数
// 要么不用第i种邮票(保持f[j]),要么用一张第i种邮票(f[j-a[i]]+1)
f[j] = min(f[j], f[j-a[i]]+1);
}
// 从面值0开始遍历,找到连续可凑出的最大面值m
for (int j=0; j<N; j++)
{
if (f[j]<=k) // 如果面值j可以用不超过k张邮票凑出
ans = j; // 更新答案
else
break; // 一旦出现不可凑出的面值,立即停止(要求1到m连续)
}
cout << ans << endl; // 输出最大的连续可凑出面值m
return 0;
}
【运行结果】
5 2
1 3
13
浙公网安备 33010602011771号