题解:洛谷 P3027 Making Money

【题目来源】

洛谷:P3027 [USACO10OCT] Making Money G

【题目描述】

FJ 又经营起了古董生意,买卖一些像奶牛圣诞树上的装饰之类的小玩意。他知道他会将他能存储的 \(N(1 \le N \le 100)\) 件不同的奶牛古董每件都卖出。

而且如果他的钱足够多他可以买他想要的任意数量的古董(即他可以购买的古董数量没有限制)。他只有 \(M(1\le M\le 10^5)\) 元钱来买古董,但他想要在他经商的第一年年末最大化他的利润(这有点难以解释)。

\(i\) 种古董采购需要花费 \(C_i(1\le C_i \le 10^5)\) 元钱,每卖掉一件可以获得 \(R_i(1\le R_i \le 10^5)\) 元钱(每卖一件的利润为 \(R_i-C_i\))。FJ 可以以任意顺序卖出他的货物。他并不需要花光他所有的钱来购买古董。

FJ 在他经商的第一年年末能得到的最大总利润(利润 = 初始钱数 - 总花费 + 总收入)是多少呢?输入数据保证这个数字不会超过 \(10^9\)

假设 FJ 只有 \(3\) 种古董而且开始时有 \(M=17\) 元钱。下面是三种古董的花费和收入。

古董 花费 收入
1 2 4
2 5 6
3 3 7

在这种情况下,FJ 应该花 \(15\) 元购买 \(5\)\(3\) 号古董,再花 \(2\) 元购买 \(1\)\(1\) 号古董,总共 \(17\) 元。他的利润是 \(5\times(7-3)+1\times(4-2)=5\times4+1\times2=22\) 元。他不能获得比这更多的利润了。

提示:第二个样例很有挑战性,但我们的答案是正确的。

【输入】

  • \(1\) 行:两个用空格分隔的整数:\(N\)\(M\)
  • \(2\) 行到第 \(N+1\) 行:第 \(i+1\) 行包含两个用空格分隔的整数:\(C_i\)\(R_i\)

【输出】

  • \(1\) 行:FJ 在给定成本和收入的情况下可以产生的最大利润。

【输入样例】

3 17
2 4
5 6
3 7

【输出样例】

22

【核心思想】

  1. 问题分析:给定 \(N\) 种古董,每种采购花费 \(C_i\),卖出收入 \(R_i\),利润 \(R_i - C_i\)。初始资金 \(M\),每种古董可无限购买。目标是最大化总利润(剩余资金 + 买卖利润)。这是一个完全背包(逆向状态定义)问题,关键在于将"剩余资金"作为状态维度,通过逆向转移实现无限选取。

  2. 算法选择

    • 完全背包(逆向DP)\(f[j]\) 表示当前剩余资金为 \(j\) 时能获得的最大额外利润
    • 逆向状态转移:从资金 \(j\) 转移到 \(j - C_i\)(购买后剩余资金减少),利润增加 \(R_i - C_i\)
    • 最终答案枚举:总利润 = 剩余资金 \(j\) + 额外利润 \(f[j]\),枚举所有 \(j\) 取最大值
  3. 关键步骤

    • 初始化memset(f, -0x3f, sizeof(f))f[M] = 0(初始剩余资金 \(M\),额外利润 \(0\)
    • 完全背包转移\(i\)\(1\)\(N\)\(j\)\(M\)\(0\)):
      • \(j \geq C_i\)f[j - C_i] = max(f[j - C_i], f[j] + (R_i - C_i))
      • 含义:用剩余资金 \(j\) 购买古董 \(i\) 后,剩余 \(j - C_i\),利润增加 \(R_i - C_i\)
    • 统计答案ans = max_{j=0}^{M}(j + f[j])
  4. 时间/空间复杂度

    • 时间复杂度:\(O(N \cdot M)\),外层 \(N\) 种古董,内层 \(M\) 个资金状态
    • 空间复杂度:\(O(M)\),一维 DP 数组
  5. 完全背包逆向状态的核心思想

    • 状态定义的逆向思维:传统背包以"花费"为状态正向累加,本题以"剩余资金"为状态逆向递减,更符合"从初始资金出发不断购买"的实际过程
    • 利润与资金的分离\(f[j]\) 只记录"额外利润",最终答案将剩余资金 \(j\) 与额外利润 \(f[j]\) 相加,得到总资金量
    • 完全背包的倒序遍历:内层 \(j\)\(M\)\(0\) 倒序,保证每种古董在同一轮外层循环中可被无限次选取(同完全背包一维优化)
    • 负无穷初始化:不可达状态设为负无穷,确保只有从初始状态 \(f[M]=0\) 可达的状态才被更新
    • 适用于"初始资源有限、可无限选取、目标是最大化最终资源"的完全背包问题

【算法标签】

普及 #完全背包

【代码详解】

#include <bits/stdc++.h>
using namespace std;

const int N = 105, M = 100005; // N:古董种类数上限, M:初始资金上限
int n, m, ans; // n:古董种类数, m:初始资金, ans:最大利润
int r[N], c[N]; // r[i]:第i种古董的收入, c[i]:第i种古董的花费
int f[M]; // f[j]:表示当前剩余资金为j时能获得的最大额外利润

int main()
{
    cin >> n >> m; // 读入古董种类数和初始资金
    for (int i=1; i<=n; i++) // 读入每种古董的花费和收入
        cin >> c[i] >> r[i];
    // 初始化DP数组为负无穷,表示初始状态下大部分资金状态不可达
    memset(f, -0x3f, sizeof(f));
    f[m] = 0; // 初始状态:剩余资金为m时,额外利润为0
    for (int i=1; i<=n; i++) // 外层循环:枚举每种古董
        for (int j=m; j>=0; j--) // 内层循环:枚举当前剩余资金(倒序,完全背包)
        {
            // 如果当前资金足够购买第i种古董
            if (j>=c[i])
                // 状态转移:用j-c[i]的资金状态更新j的资金状态
                // 购买后剩余资金减少c[i],利润增加(r[i]-c[i])
                f[j-c[i]] = max(f[j-c[i]], f[j]+ (r[i]-c[i]));
        }
    // 枚举所有可能的最终剩余资金j,计算最大总利润
    // 总利润 = 剩余资金j + 通过买卖获得的额外利润f[j]
    for (int j=0; j<=m; j++)
        ans = max(ans, j+f[j]);
    cout << ans << endl; // 输出最大利润
    return 0;
}

【运行结果】

3 17
2 4
5 6
3 7
22
posted @ 2026-08-10 12:29  团爸讲算法  阅读(6)  评论(0)    收藏  举报