洛谷 P5662 讲解(C++)

传送门

目录

思路:

前置数据定义:

定义dp数组含义:

确定dp转移方程:

dp数组初始化:

遍历顺序:

代码:


思路:

        这道题一眼就能看出用动态规划的方法,用四层 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;
}

        这个题解写的挺不容易的,希望大家点个赞。如果有问题,希望大家提出,谢谢!

  • 30
    点赞
  • 43
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值