[刷题笔记] Luogu P5662 [CSP-J2019] 纪念品

发布时间 2023-08-02 19:59:39作者: SXqwq

Problem

Description

类似于炒股票,有买进有卖出,当天可以既买进又卖出无限次,现在有若干件物品,每件物品都有一个价格,每天每件物品的价格不一致,你初始有\(m\)元钱,想要通过若干次购进卖出的操作,使得\(T\)天后你手里的钱最多。要求:\(T\)天结束你手中的股票必须全部售出。

Solution

乍看题发现如果直接dp状态很多,我们先来看看部分分:

对于10%的数据,T=1

当我们只有一天时,无论怎么搞只能有\(m\)元,直接输出\(m\)即可。这十分属于送分。

接下来考虑正解。

发现题目没有突破口了,想要dp却无法设计状态。

有一个重要的性质:题目允许当天买进当天卖出,如果我们在第\(l\)天买进,在第\(r\)天卖出,那么如果我们中间在第\(l+1\)天买进,在第\(l+1\)天卖出,在第\(l+2\)天买进,在第\(l+2\)天卖出......是不是不会影响答案?

这是本题的一个重要突破口。

我们都假设在第一天买进,在第二天早上卖出,显然我们希望第二天早上卖出的钱更多,满足局部最优解。

到这里,我们发现这是一个完全背包问题,考虑将完全背包问题的方程转换到本题中。
\(f_{i,j,k}\)表示在第\(i\)天的时候,枚举前\(j\)件物品,手中钱有\(k\)元时,第二天能卖出的最大价钱。

则满足:

\(f_{i,j,k}=max(f_{i,j,k},f_{i,j-1,k+price_{i,j}}-price_{i,j}+price_{i+1,j})\)

(\(price_{i,j}\)表示第\(i\)天时第\(j\)件物品的价钱)

解释一下:第\(j\)天若在第二天卖出的最大价钱,可以通过上一件物品的最大价钱(上一件物品手中有的钱为\(k+price_j\),因为本次购买需要消费\(price_{i,j}\)元,同时第二天卖出能获得\(price_{i+1,j}\)元。

是不是类似于完全背包!

但是这样交上去会MLE,既然和完全背包类似,那就像完全背包一样考虑优化状态,由于第\(i\)天只依赖于第\(i-1\)天,故砍掉这层状态。枚举前\(i\)件物品也可以通过按照顺序枚举控制,故只需要保存手中前有\(k\)元时的状态就可以了!(和完全背包类似的)

这样我们就只需要令\(f_k\)表示手中钱有\(k\)元时第二天能卖出的最大价钱就可以了,状态转移方程如下:

\(f_k=max(f_k,f_k-price_{i,j}+price_{i+1,j})\)

和完全背包同理,枚举价钱的时候需要从大到小枚举。这样也算是每次尝试更改本物品可以改变的最大价钱。

由于dp时每天是独立的,每天和每天之间有关联的只有每件物品的价钱和持有金币数。所以每天开始前\(f\)数组都应初始化为极小值。
每天结束后取\(f\)数组里的max即为第二天开始时手中持有的金币数。(肯定选一个最赚钱的呀

Code

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#define N 10010
#define INF 0x3f3f3f3f
using namespace std;
int f[N];
int price[N][N];
int t,n,m;
int ans = 0;
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",&price[i][j]);
	}
	int money = m; //初始化手中持有的金币数为m
	for(int i=1;i<=t;i++)
	{
	//	cout<<money<<endl;
		memset(f,-INF,sizeof(f)); //每天开始前初始化为极小值
		f[money] = money; //边界:什么都不买的时候第二天还是拥有这些钱
		for(int j=1;j<=n;j++) 
		{
			for(int k=money;k>=price[i][j];--k) //和完全背包同理,倒着搜
			{
				f[k-price[i][j]] = max(f[k-price[i][j]],f[k]-price[i][j]+price[i+1][j]); 
			}
		}
		int maxn = 0;
		for(int j=0;j<=money;j++) maxn = max(maxn,f[j]); //每天结束后的金币max即为第二天开始时持有的金币数
		money = maxn; 
	}
	cout<<money<<endl;
	return 0;
 }