## [$AcWing$ $1163$. 纪念品](https://www.acwing.com/problem/content/description/1165/) ### 一、题目描述 小伟突然获得一种超能力,他知道未来 $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**: ```cpp {.line-numbers} 6 1 100 50 20 25 20 25 50 ``` **输出样例1**: ```cpp {.line-numbers} 305 ``` **输入样例2**: ```cpp {.line-numbers} 3 3 100 10 20 15 15 17 13 15 25 16 ``` **输出样例2**: ```cpp {.line-numbers} 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 解读:比如纪念品$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 >**注:类似于邻项交换的贪心策略** 接下来的问题变成: > 假设第 $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$。 ### 三、实现代码 ```cpp {.line-numbers} #include 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; } ```