洛谷P5662 纪念品(NOIp2019普及组T3)

题目描述

小伟突然获得一种超能力,他知道未来 (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) 种纪念品的价格。

输出格式

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

输入输出样例

输入 #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 枚金币。

【数据规模与约定】

对于 (10%) 的数据,(T = 1)

对于 (30%) 的数据,(T leq 4), (N leq 4), (M leq 100),所有价格 (10 leq P_{i,j} leq 100)

另有 (15%) 的数据,(T leq 100), (N = 1)

另有 (15%) 的数据,(T = 2, N leq 100)

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

思路

首先这很明显是一道dp
作为只会dp的蒟蒻居然用了一天的时间也没想出来一道黄题,我太菜了。
其实我只是没有想到一个事实:当天我卖掉的东西可以再当天买回来,所以我完全可以理解为每到新的一天我可以把手里所有的东西卖掉,如果我觉得不值,也就是以后卖我可能会赚钱,那我可以在今天再把卖掉的东西买回来,还是不亏的。其实想到这里我觉得这个题就一点也不难了,其实就是一个完全背包,再考虑一下状态转移方程就行了。其实认真(感性)理解一下就能发现,因为我们可以在新的一天将卖掉的东西原价买回来,所以我们买东西还是卖东西其实只跟明天的价格有关,那么我们的状态转移就是买不买和卖不卖东西。如果买了这个东西我们肯定要明天卖掉(大不了再买回来),不然就不买。用 (f_i) 表示手里还剩 (i) 元钱的时候能赚到的最多的钱是多少。那么状态转移方程就是 (f_k-a_{i,j}=max(f_k-a_{i,j},f_k+a_{i+1,j}-a_{i,j})(i)(j)表示第 (i) 个物品第 (j) 天的价格, (k) 表示当前手里有 (k) 元钱)。

代码

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<queue>
using namespace std;
int t,n,m,ans;
int f[10005];
int a[110][110];
int main()
{
	scanf("%d%d%d",&t,&n,&m);
	for(int i=1;i<=t;i++){
		for(int j=1;j<=n;j++){
			scanf("%d",&a[i][j]);
		}
	}
	ans=m;//第一天早上有m元 
	for(int i=1;i<t;i++){//小于等于t也无所谓,因为最后一天肯定要都卖出去,不可能往里买,所以不会更新答案,但是准确来说应该是小于t 
		memset(f,-0x3f,sizeof(f));//每一遍循环都要初始化 
		f[ans]=ans;//如果我不买东西也不卖东西,那么手里有ans块钱最后也一定还剩ans块钱 
		for(int j=1;j<=n;j++){
			for(int k=ans;k>=a[i][j];k--){//类似于完全背包的思路,只要能买我可以一直买一样东西 
				f[k-a[i][j]]=max(f[k-a[i][j]],f[k]+a[i+1][j]-a[i][j]);//状态转移方程 
			}
		}
		int maxn=0;
		for(int j=0;j<=ans;j++){
			maxn=max(maxn,f[j]);//每次我都要看看手里最多能有多少钱 
		}
		ans=maxn;
	}
	printf("%d
",ans);
	return 0;
}
原文地址:https://www.cnblogs.com/57xmz/p/13360614.html