目录
思路:
这道题一眼就能看出用动态规划的方法,用四层 for 循环,分别遍历卖的天数,纪念品价格,
钱的拥有数量和买某个纪念品的天数,每次遍历都要取能获得钱的最大值。
但是,如果这样写,时间复杂度为O(t^2*n*m)。t的最大值是10^2,n的最大值也是10^2,
m的最大值则是10^3,最大为 10^9,会 TLE。
所以,我们需要做一个优化,来确保不会超时。
题面里有一句关键的话 “ 当日购买的纪念品也可以当日卖出换回金币 ” 。这意味着什么呢?这
意味着,如果我们要持有这个纪念品,我们可以看成第一天买,第二天卖出,第二天再买,第三天
在卖出,之后在买回来 ……
这样,我们就不用记录每天有多少纪念品了,统一认为我们今天买的纪念品,明天早上就立
刻卖掉。而且我们也不用在遍历买某个纪念品的天数,时间复杂度变成O(tnm),最差为 10^7,
不会 TLE。
但是,问题又来了,如果我们这么写的话,需要定义三维 dp 数组,dp [ i ][ j ][ k ],表示到了
第i天,我们考虑第j个纪念品,还有k元所能拿到的钱数。数组最大是10^8。在dev上可能能开这么
大, 但是,在网站上和比赛时,是开不了这么大的。
所以,我们也要进行优化。
做过背包的读者都知道,因为背包的状态是由上一个状态推导而来,不需要其他的状态了,
所以就可以压缩成一个维度,这道题也是如此。
最后,我们数组的空间,就被压缩成了一个维度。
前置数据定义:
price[ i ][ j ]:第 i 天第 j 个纪念品的价格
money: 昨天预计所能拥有的最多的钱
定义dp数组含义:
因为,我们是默认今天买入,明天卖出。所以我们能收到的钱也是明天的,今天收不到。
dp[ k ]:价格为 k 时,明天所能拥有的最多钱数为dp[ k ]
确定dp转移方程:
因为,我们是默认今天买入,明天卖出。所以,我们能收到的钱也是明天的。而今天的钱已
经花了出去,不能在收到钱。
我们能赚到(不一定是赚到,可能今天价格比明天低)的钱就是减去今天花的钱,加上明天
赚的钱。
据此,我们能推断出以下dp公式:
dp[ k - price[ i ][ j ] ] = max(dp[ k - price[ i ][ j ] ] ,dp[ k ]+price[ i+1 ][ j ] - price[ i ][ j ]);
dp数组初始化:
一开始,我们所拿到的钱是昨天预计所能赚到的最多的钱。所以,当我们所持有 money 元
时,也就是一元没花,所能拥有的也是这么多钱。
一开始,我们没有运算,所以也就不知道明天最多能拥有多少钱,就将所有都设定为未知,
未知默认为 -1。
memset( dp , -1 , sizeof( dp ) );
dp[ money ] = money;
遍历顺序:
这道题我们需要遍历卖的天数,纪念品价格和钱的拥有数量,需要三层 for 循环。但是,它
们的遍历顺序又是怎样的?
做过背包的读者都知道,完全背包是需要倒着遍历。这道题目可以不止买一个纪念品,可以
买无数个(如果有钱)。所以,我们需要倒着遍历钱。一般来说,我们是先从一元不花到全花光遍
历。但是,这道题我们是要遍历所剩钱数。所以,我们要从现在所拥有的钱(一元不花)到没有钱
(全花光)。
for( int i = 1;i < t;i ++){
for(int j =1;j <= n; j++){
for(int k = money;k >= price[ i ][ j ]; k--)
}
}
代码:
#include<bits/stdc++.h>
using namespace std;
int price[109][109],dp[10009]; //定义dp数组,price数组
int main(){
int t,n,m; //定义变量
cin>>t>>n>>m; //输入数据
for(int i=1;i<=t;i++){ //遍历天数
for(int j=1;j<=n;j++) cin>>price[i][j]; //输入第 i 天第 j 个纪念品价格
}
int money=m; //定义一开始拥有最多钱数
for(int i=1;i<t;i++){ //遍历天数(由于第一天无法获益,所以遍历 t-1 次)
memset(dp,-1,sizeof(dp)); //将所有值设定为未知
dp[money]=money; //持有 money 元时最多能赚 money 元
for(int j=1;j<=n;j++){ //遍历纪念品
for(int k=money;k>=price[i][j];k--){ //倒着遍历持有钱数
//对现在明天预计所赚钱和卖入物品明天预计所赚钱求最大值
dp[k-price[i][j]]=max(dp[k-price[i][j]],dp[k]+price[i+1][j]-price[i][j]);
}
}
int num=0; //明天拥有最多的钱数(初始为0)
for(int j=0;j<=money;j++) num=max(num,dp[j]); //取最大值
money=num; //更新
}
cout<<money; //输出答案
return 0;
}
这个题解写的挺不容易的,希望大家点个赞。如果有问题,希望大家提出,谢谢!