BZOJ1855 [Scoi2010]股票交易[单调队列dp]

题  

题面有点复杂,不概括了。


后面的状态有前面的最优解获得大致方向是dp。先是瞎想了个$f[i][j]$表示第$i$天手里有$j$张股票时最大收入(当天无所谓买不买)。

然后写了一个$O(n^4)$状转

$f[i][j]=max(max{f[k][l]-(j-l)*AP[i]},max{f[k][l]+(l-j)*BP[i]})$

这个很明显就是某一天的前w天之前是可以交易后推到这一天的,因为$i-w~i-1$这些天的状态你也不知道最优解有没有在当天进行交易。那就分为买和卖股票两部分,分别算一下最大利益再合并就行,注意保留不买也不卖的状态。买和卖每天的数量限制就转化为范围。注意题目的意思,一天也是可以既买入又卖出股票的,但是可以发现我们有一部分股票可算作卖出又买回来,这样坑定要亏,那不如不卖了,所以每天要不然买,要不然卖,要不然什么也不做

验证正确性,写了个40pts暴力

 1 #include<iostream>
 2 #include<cstdio>
 3 #include<algorithm>
 4 #include<cstring>
 5 #include<cmath>
 6 #include<queue>
 7 #define dbg(x) cerr<<#x<<"="<<x<<endl
 8 using namespace std;
 9 typedef long long ll;
10 template<typename T>inline char MIN(T&A,T B){return A>B?A=B,1:0;}
11 template<typename T>inline char MAX(T&A,T B){return A<B?A=B,1:0;}
12 template<typename T>inline T _min(T A,T B){return A<B?A:B;}
13 template<typename T>inline T _max(T A,T B){return A>B?A:B;}
14 template<typename T>inline T read(T&x){
15     x=0;int f=0;char c;while(!isdigit(c=getchar()))if(c=='-')f=1;
16     while(isdigit(c))x=x*10+(c&15),c=getchar();return f?x=-x:x;
17 }
18 const int N=2000+7;const ll INF=1e13;
19 ll f[N][N],ans;
20 int n,m,w,as,bs,ap,bp;
21 
22 int main(){//freopen("test.in","r",stdin);//freopen("tmp.out","w",stdout);
23     read(n),read(m),read(w);
24     for(register int i=1;i<=m;++i)f[0][i]=-INF;
25     for(register int i=1;i<=n;++i){
26         read(ap),read(bp),read(as),read(bs);
27         for(register int j=0;j<=m;++j){
28             f[i][j]=-INF;
29             for(register int k=0;k<=_max(0,i-w-1);++k){
30                 for(register int l=_max(0,j-as);l<=j;++l)if(f[k][l]^-INF)MAX(f[i][j],f[k][l]-(j-l)*1ll*ap);
31                 for(register int l=j+1;l<=_min(m,j+bs);++l)if(f[k][l]^-INF)MAX(f[i][j],f[k][l]+(l-j)*1ll*bp);
32             }
33             MAX(ans,f[i][j]);
34         }
35     }
36     printf("%lld
",ans);
37     return 0;
38 }
View Code

好下面想优化。观察到每一行的每一格算卖出的最大利润可以有前面一些行从这一列开始往后的一块选max转移过来,所以卖出可以倒推dp。我可以分列转移,每一列$l$可行的$k∈[1~i-w-1]$天内找一个$max{f[k][l]+(l-j)*b}$即$max{f[k][l]}+(l-j)*b$,那我只需要每列维护一个单调栈,保持栈底是j列的f最大值,然后每列直接转移.复杂度$O(n^3)$。再看我每一格是由后面的$bs[i]$格所在列转移的,每列我已经找到一个最优解,那下面我要从这几列(范围内)内找$max{f[l]+(l-j)*b}$($f[l]$为l这一列最大f值)即$max{f[l]+l*b}-j*b$,这时就可以用单调队列维护max内那玩意儿了。每行做一次,买入转态同理,从前往后做一遍。同样注意不买不卖的情况应予以保留。复杂度就成了$O(n^2)$。细节看垃圾代码

 1 #include<iostream>
 2 #include<cstdio>
 3 #include<algorithm>
 4 #include<cstring>
 5 #include<cmath>
 6 #include<queue>
 7 #define dbg(x) cerr<<#x<<"="<<x<<endl
 8 using namespace std;
 9 typedef long long ll;
10 template<typename T>inline char MIN(T&A,T B){return A>B?A=B,1:0;}
11 template<typename T>inline char MAX(T&A,T B){return A<B?A=B,1:0;}
12 template<typename T>inline T _min(T A,T B){return A<B?A:B;}
13 template<typename T>inline T _max(T A,T B){return A>B?A:B;}
14 template<typename T>inline T read(T&x){
15     x=0;int f=0;char c;while(!isdigit(c=getchar()))if(c=='-')f=1;
16     while(isdigit(c))x=x*10+(c&15),c=getchar();return f?x=-x:x;
17 }
18 const int N=2000+7;const ll INF=1e13;
19 ll f[N][N],ans;
20 int q[N],qb[N][N],qa[N][N],ta[N],tb[N];//t--stack's top
21 int n,m,w,a,b,A,B,l,r;//a--buy  b--sell  A--buying Bmax stocks  B--selling Smax stocks
22 inline ll Fb(int j,int b){return f[qb[j][1]][j]+1ll*j*b;}
23 inline ll Fa(int j,int a){return f[qa[j][1]][j]+1ll*j*a;}
24 
25 int main(){//freopen("test.in","r",stdin);//freopen("tmp.out","w",stdout);
26     read(n),read(m),read(w);
27     for(register int j=0;j<=m;++j)f[0][j]=-INF;f[0][0]=0;
28     for(register int i=1;i<=n;++i){
29         read(a),read(b),read(A),read(B);
30         for(register int j=0;j<=m;++j)f[i][j]=-INF;
31         l=1,r=0;
32         for(register int j=m;~j;--j){
33             if(i>w+1){
34                 while(tb[j]&&f[qb[j][tb[j]]][j]<=f[i-w-1][j])--tb[j];
35                 qb[j][++tb[j]]=i-w-1;
36             }
37             while(l<=r&&Fb(q[r],b)<=Fb(j,b))--r;
38             q[++r]=j;
39             while(l<=r&&q[l]>j+B)++l;
40             MAX(f[i][j],Fb(q[l],b)-1ll*j*b);
41         }
42         l=1,r=0;
43         for(register int j=0;j<=m;++j){
44             if(i>w+1){
45                 while(ta[j]&&f[qa[j][ta[j]]][j]<=f[i-w-1][j])--ta[j];
46                 qa[j][++ta[j]]=i-w-1;
47             }
48             while(l<=r&&Fa(q[r],a)<=Fa(j,a))--r;
49             q[++r]=j;
50             while(l<=r&&q[l]<j-A)++l;
51             MAX(f[i][j],Fa(q[l],a)-1ll*j*a);
52             MAX(ans,f[i][j]);
53         }
54     }
55     printf("%lld
",ans);
56     return 0;
57 }

嗯上边的思路是我个人想的,有部分地方想繁掉了,于是A掉之后看了一眼题解,发现自己真傻。。单调栈完全没必要开出来的。

我要找每一列的合法最大值转移,不用单调栈维护,只要在$f[i-w-1][j]$直接从$f[i-w-2][j]$继承过来就好了,也不会影响合法性,我前一天做了交易,今天不动,所以没事,这样我在这里就保留了j这一列最大f值,因为它来自于上面数行中$fmax$和当前行dp取得的f的最大值。所以一开始朴素方程就是$O(n^3)$的。这个继承获得之前最大值的办法希望记住。

原文地址:https://www.cnblogs.com/saigyouji-yuyuko/p/10454439.html