问题概述:
量化贸易的问题根据百度百科结合算法竞赛可以总结为:
已知将来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;
}