P2569 [SCOI2010] 股票交易

liuboom發表於2024-12-06

P2569 [SCOI2010] 股票交易

[SCOI2010] 股票交易

題目描述

最近 \(\text{lxhgww}\) 又迷上了投資股票,透過一段時間的觀察和學習,他總結出了股票行情的一些規律。

透過一段時間的觀察,\(\text{lxhgww}\) 預測到了未來 \(T\) 天內某隻股票的走勢,第 \(i\) 天的股票買入價為每股 \(AP_i\),第 \(i\) 天的股票賣出價為每股 \(BP_i\)(資料保證對於每個 \(i\),都有 \(AP_i \geq BP_i\)),但是每天不能無限制地交易,於是股票交易所規定第 \(i\) 天的一次買入至多隻能購買 \(AS_i\) 股,一次賣出至多隻能賣出 \(BS_i\) 股。

另外,股票交易所還制定了兩個規定。為了避免大家瘋狂交易,股票交易所規定在兩次交易(某一天的買入或者賣出均算是一次交易)之間,至少要間隔 \(W\) 天,也就是說如果在第 \(i\) 天發生了交易,那麼從第 \(i+1\) 天到第 \(i+W\) 天,均不能發生交易。同時,為了避免壟斷,股票交易所還規定在任何時間,一個人的手裡的股票數不能超過 \(\text{MaxP}\)

在第 \(1\) 天之前,\(\text{lxhgww}\) 手裡有一大筆錢(可以認為錢的數目無限),但是沒有任何股票,當然,\(T\) 天以後,\(\text{lxhgww}\) 想要賺到最多的錢,聰明的程式設計師們,你們能幫助他嗎?

輸入格式

輸入資料第一行包括 \(3\) 個整數,分別是 \(T\)\(\text{MaxP}\)\(W\)

接下來 \(T\) 行,第 \(i\) 行代表第 \(i-1\) 天的股票走勢,每行 \(4\) 個整數,分別表示 \(AP_i,\ BP_i,\ AS_i,\ BS_i\)

輸出格式

輸出資料為一行,包括 \(1\) 個數字,表示 \(\text{lxhgww}\) 能賺到的最多的錢數。

提示

  • 對於所有的資料,\(1\leq BP_i\leq AP_i\leq 1000,1\leq AS_i,BS_i\leq\text{MaxP}\)

DP好題

首先,題目規定第x天交易了那麼
第 [x+1,x+w]天都不能交易
也就是說第x天交易後的下一個可交易日是x+1+w所以我們讓w++
即對第x天,它的上一個可交易日是x-w

\(dp[i][j]\)表示在第i天,手上持有j股時的最大收益

顯然,我們有:
對於購買:\(dp[i][j]=max(dp[i-w][k]-(j-k)*AP_i)| j-k \le AS_i\)
對於賣出:\(dp[i][j]=max(dp[i-w][k]+(k-j)*BP_i)| k-j \le BS_i\)

但是如果我們真的這麼寫的話,對於每天的轉移我們似乎浪費了很多時間

所以我們考慮單調佇列最佳化:
我們發現右半部分的式子分別可以寫成:
\(max(dp[i-w][k]+k*AP_i)-j*AP_i | j-k \le AS_i\)
\(max(dp[i-w][k]+k*AP_i)-j*BP_i | j-k \le BS_i\)
顯然,\(max\)外的數字並不影響\(max\)內的單調性
所以我們使用單調佇列來維護\(max\)然後直接轉移就好了
時間複雜度O(TW)

然後這題就做完了 (經典結束語)

Code

#include<bits/stdc++.h>
#define int long long 
const int N=2005;
const int inf=1e17;
using namespace std;
int n,m,w;
int dp[N][N];
int q[N<<1];
void init()
{
	for(int i=0;i<N;i++)
	{
		for(int j=0;j<N;j++)
		{
			dp[i][j]=-inf;
		}
	}
}
void work()
{
	init();
	cin>>n>>m>>w;
	w++;
	for(int i=1,ap,bp,as,bs;i<=n;i++)
	{
		scanf("%lld%lld%lld%lld",&ap,&bp,&as,&bs);
		for(int j=0;j<=as;j++)dp[i][j]=-1ll*ap*j;
		for(int j=0;j<=m;j++)dp[i][j]=max(dp[i][j],dp[i-1][j]);
		//copy
		if(i-w<1)continue;
		int st=1,ed=0;
		for(int j=0;j<=m;j++)//之前持有k,現在買j-k使現在有j: 
		{
			while(st<=ed){if(j-q[st]>as)st++;else break;}//刪掉購買數過多的 
			while(st<=ed){if(dp[i-w][q[ed]]+q[ed]*ap<=dp[i-w][j]+j*ap)ed--;else break;}//最佳化之前買的少還賺的少的
			q[++ed]=j;
			if(st<=ed)dp[i][j]=max(dp[i][j],dp[i-w][q[st]]+q[st]*ap-j*ap); 
		}
		st=1,ed=0;
		for(int j=m;j>=0;j--)//之前持有k,現在賣出k-j使現在有j: 
		{
			while(st<=ed){if(q[st]-j>bs)st++;else break;}//刪掉賣出數過多的 
			while(st<=ed){if(dp[i-w][q[ed]]+q[ed]*bp<=dp[i-w][j]+j*bp)ed--;else break;}//最佳化之前買的少還賺的少的
			q[++ed]=j;
			if(st<=ed)dp[i][j]=max(dp[i][j],dp[i-w][q[st]]+q[st]*bp-j*bp); 
		}
	}
	int ans=0;
	for(int i=0;i<=m;i++)
	{
		ans=max(ans,dp[n][i]);
	}
	printf("%lld",ans);
}
#undef int
int main()
{
	work();
	return 0;
}