Files
python/TangDou/CSP-J/AcWingTraining/T7/1163.md
HuangHai 1f397eca87 'commit'
2025-08-30 18:35:01 +08:00

6.8 KiB
Raw Blame History

AcWing 1163. 纪念品

一、题目描述

小伟突然获得一种超能力,他知道未来 T 天 N 种纪念品每天的价格。

某个纪念品的价格是指购买一个该纪念品所需的金币数量,以及卖出一个该纪念品换回的金币数量。

每天,小伟可以进行以下两种交易无限次:

任选一个纪念品,若手上有足够金币,以当日价格购买该纪念品,注意 同一个纪念品可以在同一天重复买;

卖出持有的任意一个纪念品,以当日价格换回金币。 每天卖出纪念品换回的金币可以立即用于购买纪念品,当日购买的纪念品也可以当日卖出换回金币。

当然,一直持有纪念品也是可以的。

T 天之后,小伟的超能力消失。

因此他一定会在第 T 天卖出所有纪念品换回金币。

小伟现在有 M 枚金币,他想要在超能力消失后拥有尽可能多的金币。

输入格式 第一行包含三个正整数 $T,N,M$,相邻两数之间以一个空格分开,分别代表未来天数 $T$,纪念品数量 $N$,小伟现在拥有的金币数量 $M$。

接下来 T 行,每行包含 N 个正整数,相邻两数之间以一个空格分隔。第 i 行的 N 个正整数分别为 $P_{i,1},P_{i,2},……,P_{i,N}$,其中 P_{i,j} 表示第 i 天第 j 种纪念品的价格。

输出格式 输出仅一行,包含一个正整数,表示小伟在超能力消失后最多能拥有的金币数量。

数据范围 对于 10\% 的数据,$T=1$。

对于 30\% 的数据,$T≤4,N≤4,M≤100$,所有价格 $10≤P_{i,j}≤100$。

对于 15\% 的数据,$T≤100,N=1$。

对于 15\% 的数据,$T=2,N≤100$。

对于 100\% 的数据,$T≤100,N≤100,M≤10^3$,所有价格 $1≤P_{i,j}≤10^4$,数据保证任意时刻,小明手上的金币数不可能超过 $10^4$。

输入样例1:

6 1 100
50
20
25
20
25
50

输出样例1:

305

输入样例2:

3 3 100
10 20 15
15 17 13
15 25 16

输出样例2:

217

样例解释 样例#1: 最佳策略是:

  • 第二天花光所有 100 枚金币买入 5 个纪念品 $1$;

  • 第三天卖出 5 个纪念品 $1$,获得金币 125 枚;

  • 第四天买入 6 个纪念品 $1$,剩余 5 枚金币;

  • 第六天必须卖出所有纪念品换回 300 枚金币,第四天剩余 5 枚金币,共 305 枚金币。

超能力消失后,小伟最多拥有 305 枚金币。

样例#2: 最佳策略是:

  • 第一天花光所有金币买入 10 个纪念品 $1$;

  • 第二天卖出全部纪念品 1 得到 150 枚金币并买入 8 个纪念品 2 和 1 个纪念品 $3$,剩余 1 枚金币;

  • 第三天必须卖出所有纪念品换回 216 枚金币,第二天剩余 1 枚金币,共 217 枚金币。

超能力消失后,小伟最多拥有 217 枚金币。

二、算法解析

(贪心,$DP$) $O(TNM)$

关键点

在某一种方案中,如果我们在第$i$天买入第$j$天卖出,其中 $i<j$,则等价于在第 $i$天买入,第 i+1 天卖出,第 i+1 天再买入, …, 在第 j 天卖出。

解读:比如纪念品A,我们假设最优策略是第$1$天买入,第$7$天卖出是最优的,可以选择的等价策略是第$1$天买入,第$2$天卖出,第$2$天再买入,第$3$天卖出,...,第$7$天卖出,这与我们的原始策略是一样的。 按数学思路来说明一下,第$1$天买入,第$7$天卖出=P_7-P_1 第$1$天买入,第$2$天卖出,第$2$天买入,第$3$天卖出,...第$7$天卖出=-P_1+P_2-P_2+P_3-P_3+P_4-P_4+P_5-P_5+P_6-P_6+P_7=P_7-P_1 两者是一样的。

这样的话,所有交易不会跨两天,因此目标就变成了 贪心策略:先尽可能使第二天的钱最多,再尽可能使第三天的钱最多,依次类推直到最后一天。

解读:这个为什么是正确的呢? 可以用反证法:如果我第$2$天亏一些,有没有可能让第$3$天更赚呢? 这是不可能的,比如第$2$天我剩下的钱是可能是$m_1$个金币,也可能是$m_2$个金币,而且满足m_1<m_2,那么$m_2$一定比$m_1$要好,这是显然的,为什么呢? 因为我在购买下一天的纪念品的时候,肯定是手中的钱越多越多,因为如果钱少,那么可能有些纪念品我就买不起,就会少一些机会,结果不可能变得更好。$m_1$块钱可以购买的所有纪念品,$m_2$块钱肯定是可以购买到的,反过来就不是了。$m_2$考虑的所有方案,一定包含$m_1$考虑的所有方案,所以$m_2$考虑所有方案的最大值一定比$m_1$考虑的所有方案的最大值要大。所以,得到结论:我们每天都要争取最大值,这样,最终就是最大值。

注:类似于邻项交换的贪心策略

接下来的问题变成:

假设第 i 天的钱数是 $m_i$,那么第 i+1 天的钱数最多是多少呢?

这是一个经典的 完全背包 问题:

  • 背包容量是 $m_i$;
  • 每个股票都是一种物品,体积是第 i 天买入的价格,价值在第 i 天买入第 i+1 天卖出的收益; 每天可以进行无限次交易,因此每种物品都是可以用无限次; 那么第 i+1 天的最大收益 m_{i+1} 就是 m_i 加上背包模型所求出的最大收益。

本题时间复杂度恰好是 $10^8$,比较极限,因此可以加一些优化:

  • 只有当某支股票的收益为正数时,才考虑使用该物品。

时间复杂度

一共有 T≤100 天,每天有 N≤100 支股票,每天的钱数最大是 $M≤10000$。

算法中一共会算 T−1 次背包模型,每算一次的时间复杂度是 $O(NM)$,因此总时间复杂度是 $O(TNM)=10^8$。

三、实现代码

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

const int N = 1010, M = 10010;

int t, n, m;
int f[M];
int w[N][N];

int main() {
    // 加快读入
    ios::sync_with_stdio(false), cin.tie(0);
    cin >> t >> n >> m;
    for (int i = 1; i <= t; i++)
        for (int j = 1; j <= n; j++)
            cin >> w[i][j];

    for (int i = 1; i < t; i++) { // t-1轮背包
        memset(f, 0, sizeof f);
        for (int j = 1; j <= n; j++)
            if (w[i + 1][j] > w[i][j]) // 优化:只有在价值为正数时才会考虑使用该物品
                for (int k = w[i][j]; k <= m; k++)
                    f[k] = max(f[k], f[k - w[i][j]] + w[i + 1][j] - w[i][j]);
        m += f[m];
    }

    printf("%d\n", m);

    return 0;
}