[20220117]P2569 [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}\) 想要赚到最多的钱,聪明的程序员们,你们能帮助他吗?

分情况讨论

本人曾经对经济学比较感兴趣,所以关系还是能弄懂得。

准备阶段

主公可以发动技能

把所有状态赋值为\(-inf\),可以使用

memset(f,0x80,sizeof(f));

在本文中,\(f[i][j]\)指在第\(i\)天持有\(j\)支股票的赚钱数。

空手套白狼,凭空购买

当你没有钱时,直接购买(还欠钱)。

\(f[i][j]=-AP_i \times j\)。

不买不卖,虚度光阴

\(f[i][j]=max \{ f[i-1][j],f[i][j]\}\)

牛市来了,买买买

首先,每次交易至少间隔\(W\)天,所以上一次交易至少在\(i-W-1\)天。

又\(\because\)状态转移方程\(f[i][j]=max \{ f[i-1][j],f[i][j]\}\)把最优解移到了\(i\)更大的\(f[i][j]\),所以\(f[i-W-1]\)一定最优。

设在\(i-W-1\)天拥有\(k\)张股票。

那么\(j \gt k\),购买股票只能购买正整数(\(Z^+\))张嘛!

购买了\((j-k)\)张股票,花费\((j-k)\times AP_i\)。

股票一次不能买太多,只能买\(AS_i\)支,所以)\((j-AS_i) \le k \lt j\)

整理,得$f[i][j]=max { f[i][j], f[i-w-1][k]-(j-k) \times AP_i} \(   \)((j-AS_i) \le k \lt j)$

熊市来了,卖卖卖

直接写状态转移方程吧:

\(f[i][j]=max \{ f[i][j],f[i-w-1][k]+(k-j) \times BP_i \}\)   \((j \lt k \le(j+BS_i))\)

股票交易v1.0

#include<bits/stdc++.h>
using namespace std;

int t,m,w;
int f[2005][2005];
int ans=INT_MIN;

int main(){
	ios::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
	memset(f,0x80,sizeof(f)); // f->(-inf)[] 
	cin>>t>>m>>w;
	for(int i=1;i<=t;i++){
		int ap,bp,as,bs;
		cin>>ap>>bp>>as>>bs;
		for(int j=0;j<=as;j++){
			f[i][j]=-1*ap*j;
		}
		for(int j=0;j<=m;j++){
			f[i][j]=max(f[i][j],f[i-1][j]);
		}
		for(int j=0;j<=m;j++){
			for(int k=j-as;k>=0&&k<j;k++){
				f[i][j]=max(f[i][j],f[i-w-1][k]-(j-k)*ap);
			}
		}
		for(int j=m;j>=0;j--){
			for(int k=j+1;k<=j+bs;k++){
				f[i][j]=max(f[i][j],f[i-w-1][k]+(k-j)*bp);
			}
		}
	}
	for(int i = 0 ; i <= m ; i++)
        ans = max(ans , f[t][i]);
    printf("%d",ans);
	return 0;
}

结果只有\(\color{red}\textbf{10分}\)。

股票交易v2.0

发现i可能会小于或等于w,这时坐标直接为负数。

解决代码:

if(i<=w){
	continue;
}

最终代码:

#include<bits/stdc++.h>
using namespace std;

int t,m,w;
int f[2005][2005];
int ans=INT_MIN;

int main(){
	ios::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
	memset(f,0x80,sizeof(f)); // f->(-inf)[] 
	cin>>t>>m>>w;
	for(int i=1;i<=t;i++){
		int ap,bp,as,bs;
		cin>>ap>>bp>>as>>bs;
		for(int j=0;j<=as;j++){
			f[i][j]=-1*ap*j;
		}
		for(int j=0;j<=m;j++){
			f[i][j]=max(f[i][j],f[i-1][j]);
		}
		if(i<=w){
			continue;
		}
		for(int j=0;j<=m;j++){
			for(int k=j-as;k>=0&&k<j;k++){
				f[i][j]=max(f[i][j],f[i-w-1][k]-(j-k)*ap);
			}
		}
		for(int j=m;j>=0;j--){
			for(int k=j+1;k<=j+bs;k++){
				f[i][j]=max(f[i][j],f[i-w-1][k]+(k-j)*bp);
			}
		}
	}
	for(int i = 0 ; i <= m ; i++)
        ans = max(ans , f[t][i]);
    printf("%d",ans);
	return 0;
}

\(\color{orange}\textbf{60}\)分。

滑动窗口优化 | 股票交易v3.0

#include<bits/stdc++.h>
using namespace std;

int t,m,w;
int f[2005][2005],q[2005];
int ans=INT_MIN;

int main(){
	ios::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
	memset(f,0x80,sizeof(f)); // f->(-inf)[] 
	cin>>t>>m>>w;
	for(int i=1;i<=t;i++){
		int ap,bp,as,bs;
		cin>>ap>>bp>>as>>bs;
		for(int j=0;j<=as;j++){
			f[i][j]=-1*ap*j;
		}
		for(int j=0;j<=m;j++){
			f[i][j]=max(f[i][j],f[i-1][j]);
		}
		if(i<=w){
			continue;
		}
//		for(int j=0;j<=m;j++){
//			for(int k=j-as;k>=0&&k<j;k++){
//				f[i][j]=max(f[i][j],f[i-w-1][k]-(j-k)*ap);
//			}
//		}
//		for(int j=m;j>=0;j--){
//			for(int k=j+1;k<=j+bs;k++){
//				f[i][j]=max(f[i][j],f[i-w-1][k]+(k-j)*bp);
//			}
//		}
		int left=1,right=0;
		for(int j=0;j<=m;j++){
			while(left<=right&&q[left]<j-as){
				left++;
			}
			while(left<=right&&f[i-w-1][q[right]]+q[right]*ap<=f[i-w-1][j] + j * ap){
				right--;
			}
			q[++right]=j;
			if(left<=right){
				f[i][j]=max(f[i][j],f[i-w-1][q[left]]+q[left]*ap-j*ap);
			}
		}
		left=1,right=0;
		for(int j=m;j>=0;j--){
			while(left<=right&&q[left]>j+bs){
				left++;
			}
			while((left<=right)&&f[i-w-1][q[right]]+q[right]*bp<=f[i-w-1][j]+j*bp){
				right--;
			}
			q[++right]=j;
			if(left<=right){
				f[i][j]=max(f[i][j],f[i-w-1][q[left]]+q[left]*bp-j*bp);
			}
		}
	}
	for(int i = 0 ; i <= m ; i++){
		ans = max(ans , f[t][i]);
	}
    printf("%d",ans);
	return 0;
}

\(\color{green}\textbf{100分}\)

上一篇:二分查找基础算法总结


下一篇:简单的排序算法