经典问题之量化贸易

问题概述:

量化贸易的问题根据百度百科结合算法竞赛可以总结为:
已知将来n天某商品的价格,每天你可以有三件事(只能做一个):

1.不买不卖

2.以当天价格买进

3.以当天价格卖出
刚开始你不持有商品,同时保证n天之后你同样不在持有商品,要求你能获得的最大利润。


思路分析:


1.裸的二维dp.定义f[i][j]表示第i天持有j件商品的最大利润。

依据题目,我们很快可以得出

f[i][j]=max(f[i][j],f[i-1][j-1]-a[i])//买

f[i][j]=max(f[i][j],f[i-1][j+1]+a[i])//卖

f[i][j]=max(f[i][j],f[i-1][j])//不买不卖


参考程序:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<cmath>
using namespace std;
int n,a[1001],f[1010][1010];
int main(){
	freopen("trade.in","r",stdin);
	freopen("trade.out","w",stdout);
	cin>>n;
	for (int i=1;i<=n;i++) scanf("%d",&a[i]);
	f[0][0]=0;f[1][0]=0;f[1][1]=-a[1];
	for (int i=2;i<=n;i++){
	  f[i][0]=max(f[i-1][0],f[i-1][1]+a[i]);
	  for (int j=1;j<i-1;j++) 
		f[i][j]=max(max(f[i-1][j],f[i-1][j-1]-a[i]),f[i-1][j+1]+a[i]);
	  f[i][i-1]=max(f[i-1][i-1],f[i-1][i-2]-a[i]);
    }
	cout<<f[n][0];
	fclose(stdin);fclose(stdout);
}

我们很快发现这种做法并不优,效率O(n^2)。在某些极端数据中会超时。
而我们也发现这种dp并没有办法用数据结构优化。所以改变思路:


再度分析:


我相信对于这个问题,很多人第一感觉会是贪心,那么贪心行不行呢?我们来模拟一个样例吧:

10
15418 40328 95724 64132 84669 61094 10410 42672 79197 65764
ans:216526
首先对于第一天二元组(0,15418)我们加入一个序列。

对于第二天40328,显然在序列中有一个数15418比40328小,我们将二元组(0,15418)删除,改作(40328,15418)

对于第三天95724,在序列中找到一个二元组的first——40328小于95724,所以在95724时卖显然更优,所以将二元组(40328,15418)删除,改作(92724,15418),又因为40328显然不能再作为卖的价格了,我们只可能在那天买进,所以加入二元组(0,40328),

对于第四天64132,找到40328小于64132,所以加入二元组(64132,40328)

对于第五天84669,找到64132小于84669,所以加入二元组(84669,40328),(0,64132)

对于第六天61094,找不到比其小的,加入二元组(0,61094)

对于第七天10410,加入(0,10410)

第八天42672,加入(42672,10410),第九天79197,加入(79197,10410)和(0,42672),第十天65764,加入(65764,42672),

最后将所有的first不为0的二元组取出,则有:

92724 15418

84669 40328

79197 10410

65764 42672

于是得到思路:对于一天,取出二元组中first最小的,若该first大于当天卖出价格,显然没什么可做,只能加入二元组(0,a[i]),若小于,则表明在现在这天卖更划算,所以删除取出的二元组,加入二元组(a[i],second),(0,first),最后统计的时候将所有二元组取出一一做差累计即可。

参考程序:

#include<cstdio>
#include<algorithm>
#include<queue>
#include<ctime>
using namespace std;
const int maxn=110000;
struct Node{
	long long x,y;
	bool operator < (const Node k)const{
		long long tx=x,tkx=k.x;
		if (x==0)tx=y;
		if (k.x==0)tkx=k.y;
		if (tx!=tkx)return tx>tkx;
		return y>k.y;	
	}
	Node(int x,int y):x(x),y(y){}
	Node(){}
};
priority_queue<Node> Q;
int n;
int a[maxn];
bool ok[maxn];
int main(){
	freopen("trade.in","r",stdin);
	freopen("trade.out","w",stdout);
	while (scanf("%d",&n)==1){
		long long ans=0;
		for (int i=1;i<=n;i++){
			scanf("%d",&a[i]);
			if (Q.empty()){Q.push(Node(0,a[i]));continue;}
			Node tt=Q.top();Q.pop();
			if (tt.x==0){
				if (tt.y<a[i])Q.push(Node(a[i],tt.y));
				else {
					Q.push(Node(0,a[i]));
					Q.push(Node(0,tt.y));
				}
			}else{
				if (tt.x>a[i]){
				    Q.push(Node(tt.x,tt.y));
				    Q.push(Node(0,a[i]));
				}else{
					Q.push(Node(a[i],tt.y));
					Q.push(Node(0,tt.x));
				}
			}
		}
		while (!Q.empty()){
			Node tt=Q.top();Q.pop();
			if (tt.x!=0)ans+=tt.x-tt.y;
		}
	    printf("%lld\n%.3f\n",ans,(double)clock()/CLOCKS_PER_SEC);	
	}
	return 0;
}

用100000的极端数据后发现要6s之多!表明仍不够快!

最后的优化!


我们不再维护二元组,而仅仅维护单个的int同时加入son数组表明买与卖的关系,从而坚守pop和push的次数,最终优化。


令son[i]表明第i天卖的话在那一天买,若son[i]=0表明第i天买。
那么现在取出最小的a[j],若a[j]<a[i],那么结果立马加a[i]-a[j],为什么,先看下去:
若son[j]=0,表明第j天本来就是买的,那么第i天卖,可以搭配,此时最优结果加a[i]-a[j],将j删去,
若son[j]!=0,表明第j天本来是卖的,而第i天更优,所以最优结果加为a[i]-a[son[j]]=a[i]-a[j]+a[j]-a[son[j]],要知道,我们在搭配j的时候已经给最优结果加了a[j]-a[son[j]]了!
此时将son[j]置为0,son[i]=j,然后将j加入优先队列。最后再将i加入优先队列。

//STL的优先队列10组100000共1.3~1.7s,手写堆1.1s


参考程序:

#include<cstdio>
#include<algorithm>
#include<ctime>
#include<cstring>
using namespace std;
const int maxn=110000;
int n,size=0;
int son[maxn];
int a[maxn];
int h[maxn];
void push(int k){
	h[++size]=k;
	int now=size;
	while (now>1){
		int fa=now>>1;
		if (a[h[fa]]>a[h[now]]){
			int tmp=h[fa];h[fa]=h[now];h[now]=tmp;
			now>>=1;
		}else break;
	}
}
void pop(){
	h[1]=h[size--];
	int now=1;
	while (now<<1<=size){
		int fa=2*now;
		if (fa<size && a[h[fa+1]]<a[h[fa]])fa++;
		if (a[h[now]]>a[h[fa]]){
			int t=h[now];h[now]=h[fa];h[fa]=t;
		    now=fa;
		}else break;
	}	
}
int main(){
	freopen("trade.in","r",stdin);
	freopen("trade.out","w",stdout);
	int T=0;
	while (scanf("%d",&n)==1){
		long long ans=0;
		memset(son,0,sizeof(son));
		size=0;
		for (int i=1;i<=n;i++){
			scanf("%d",&a[i]);
			if (size){
			    int j=h[1];
			    if (a[i]>a[j]){
				    ans+=a[i]-a[j];
				    if (son[j]){son[j]=0;son[i]=j;}
					else{pop();son[i]=j;}
			    }
			}
			push(i);
		}
	    printf("Case #%d: %lld\n",++T,ans);	
	}
	return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值