|
|
## [$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<j$,则等价于在第 $i$天买入,第 $i+1$ 天卖出,第 $i+1$ 天再买入, …, 在第 $j$ 天卖出。
|
|
|
|
|
|
> <font color='red' size=4><b>解读</b></font>:比如纪念品$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$
|
|
|
> 两者是一样的。
|
|
|
|
|
|
这样的话,所有交易不会跨两天,因此目标就变成了 **贪心策略**:先尽可能使第二天的钱最多,再尽可能使第三天的钱最多,依次类推直到最后一天。
|
|
|
> <font color='red' size=4><b>解读</b></font>:这个为什么是正确的呢?
|
|
|
> 可以用反证法:如果我第$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$。
|
|
|
|
|
|
|
|
|
### 三、实现代码
|
|
|
```cpp {.line-numbers}
|
|
|
#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;
|
|
|
}
|
|
|
``` |