题解:洛谷 P2725 邮票

【题目来源】

洛谷:P2725 [USACO3.1] 邮票 Stamps

【题目描述】

给一组 \(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

【核心思想】

  1. 问题分析:给定 \(n\) 种邮票面值(每种无限张)和最多贴 \(k\) 张的限制,求最大的正整数 \(m\),使得 \(1\)\(m\) 的每个面值都能用不超过 \(k\) 张邮票表示。这是一个完全背包的变形问题,核心在于用 DP 计算每个面值所需的最少邮票张数,再找到最长的连续可达前缀。

  2. 算法选择

    • 完全背包\(f[j]\) 表示凑出面值 \(j\) 所需的最少邮票张数
    • 正序遍历:每种邮票可无限使用,内层循环正序枚举面值
    • 连续判定:从 \(0\) 开始遍历,找到最后一个满足 \(f[j] \leq k\) 的连续面值
  3. 关键步骤

    • 初始化:读取 \(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\)
  4. 时间/空间复杂度

    • 时间复杂度:\(O(n \times N)\),其中 \(N\) 为 DP 数组上限(需覆盖可能的 \(m\) 值)
    • 空间复杂度:\(O(N)\),一维 \(f\) 数组
  5. 完全背包的核心思想

    • 无限物品模型:每种邮票可无限使用,正序遍历 \(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
posted @ 2026-08-11 11:26  团爸讲算法  阅读(7)  评论(0)    收藏  举报