使用单调队列优化dp 废话
对与一些dp的转移方程,我们可以通过拆使它与某个区间的最值相关。
这时可以用单调队列算出区间最值,进行优化。
给出一个长度为 \(n\) 的整数序列,从中找出一段长度不超过 \(m\) 的连续子序列,使得整个序列的和最大。
设 \(sum_i\) 为 \(i\) 的前缀和,易得答案为:
\[\max_\limits{1\le i\le n}\{sum_i-\min_\limits{i-m\le k\le i-1}\{sum_k\}\} \]其中 \(\min_\limits{i-m\le k\le i-1}\{sum_k\}\) 这部分可以用单调队列快速求出。
那么算起来就变得简单多了。
#include<bits/stdc++.h> #define _for(i,a,b) for(int i=a;i<=b;++i) #define for_(i,a,b) for(int i=a;i>=b;--i) #define ll long long using namespace std; const int N=3e5+10; int n,m,a[N],sum[N],f[N],ans; int q[N],h=1,t=0; inline int rnt(){ int x=0,w=1;char c=getchar(); while(c<'0'||c>'9'){if(c=='-')w=-1;c=getchar();} while(c>='0'&&c<='9')x=(x<<3)+(x<<1)+(c^48),c=getchar(); return x*w; } void tmp(int k){ while(h<=t&&q[h]<k-m)++h; ans=max(ans,sum[k]-sum[q[h]]); while(h<=t&&sum[q[t]]>sum[k])--t; q[++t]=k; } int main(){ n=rnt(),m=rnt(); _for(i,1,n){ a[i]=rnt(); sum[i]=sum[i-1]+a[i]; tmp(i); } printf("%d\n",ans); return 0; }
给定一个 \(n\ (1\le n\le 10^5)\) 个元素的序列,可任选多个数,要求任意一段连续选的数长度不能超过 \(k\)。
\(1\le N\le 10^5\)
设 \(f_{i,0/1}\) 表示第 \(i\) 个数选(1)或是不选(0),\(sum_i\) 表示 \(i\) 的前缀和。
转移方程为:
\[f_{i,0}=\max\{f_{i-1,0},f_{i-1,1}\}\\\ f_{i,1}=\max_\limits{i-k\le j<i}\{f_{j,0}+(sum_i-sum_j)\} \]\(f_{i,1}\) 转移方程可以拆成:
\[f_{i,1}=sum_i+\max_\limits{i-k\le j<i}\{f_{j,0}-sum_j\} \]用单调队列维护 \(f_{j,0}-sum_j\) 即可。
#include<bits/stdc++.h> #define _for(i,a,b) for(ll i=a;i<=b;++i) #define for_(i,a,b) for(ll i=a;i>=b;--i) #define ll long long using namespace std; const ll N=3e5+10; ll n,k,a[N],sum[N],f[N][2],ans; ll q[N],h=1,t=0; inline ll rnt(){ ll x=0,w=1;char c=getchar(); while(c<'0'||c>'9'){if(c=='-')w=-1;c=getchar();} while(c>='0'&&c<='9')x=(x<<3)+(x<<1)+(c^48),c=getchar(); return x*w; } void tmp(ll i){ while(h<=t&&q[h]<i-k)++h; f[i][1]=f[q[h]][0]+sum[i]-sum[q[h]]; while(h<=t&&f[q[t]][0]-sum[q[t]]<f[i][0]-sum[i])--t; q[++t]=i; } int main(){ n=rnt(),k=rnt(); tmp(0); _for(i,1,n){ a[i]=rnt(); sum[i]=sum[i-1]+a[i]; f[i][0]=max(f[i-1][0],f[i-1][1]); tmp(i); } printf("%lld\n",max(f[n][0],f[n][1])); return 0; }
给出一个 \(N*M\) 的船上地图,有空地也有家具。
船上有 \(K\) 段时间,在每段时间都会往不同的方向倾斜,钢琴也会朝着那个方向倾斜,但不允许碰上家具。
求钢琴最长的滑行距离。
\(1\le N,M\le200,K\le200\)
设 \(f_{i,j,k}\) 表示在第 \(i\) 段时间滑行到 \(j,k\) 的位置。
那么转移方程就是:
\[f_{i,j,k}=\max_\limits{(jj,kk)是i时刻可以滑到(j,k)的点}{f_{i,jj,kk}+|jj-j|+|kk-k|} \]瞎写的
按着倾斜的方向遍历,再用单调队列优化优化就好了。
#include<bits/stdc++.h> #define _for(i,a,b) for(int i=a;i<=b;++i) #define for_(i,a,b) for(int i=a;i>=b;--i) #define ll long long using namespace std; const int N=210,inf=0x3f3f3f3f; int n,m,x,y,k,mp[N][N]; int f[N][N][N],la[N][N][N],ans; inline ll rnt(){ ll x=0,w=1;char c=getchar(); while(c<'0'||c>'9'){if(c=='-')w=-1;c=getchar();} while(c>='0'&&c<='9')x=(x<<3)+(x<<1)+(c^48),c=getchar(); return x*w; } inline char rch(){ char c=getchar(); while(c!='.'&&c!='x')c=getchar(); return c; } struct dq{//deque int q[N],h,t; void nw(){ memset(q,0,sizeof(q)); h=1,t=0; } bool empty(){return h>t;} int front(){return q[h];} int back(){return q[t];} void pop_f(){++h;} void pop_b(){--t;} void push(int x){q[++t]=x;} }; void dp(int d,int s,int t,int fx){ int len=(t-s+1); if(fx==1){ _for(j,1,m){ dq q;q.nw(); for_(i,n,1){ if(mp[i][j]){ while(!q.empty())q.pop_f(); continue; } while(!q.empty()&&q.front()>i+len)q.pop_f(); while(!q.empty()&&f[d-1][q.back()][j]+q.back()-i<f[d-1][i][j])q.pop_b(); q.push(i); if(f[d-1][q.front()][j]>-1) f[d][i][j]=f[d-1][q.front()][j]+q.front()-i; ans=max(ans,f[d][i][j]); } } } else if(fx==2){ _for(j,1,m){ dq q;q.nw(); _for(i,1,n){ if(mp[i][j]){ while(!q.empty())q.pop_f(); continue; } while(!q.empty()&&q.front()<i-len)q.pop_f(); while(!q.empty()&&f[d-1][q.back()][j]+i-q.back()<f[d-1][i][j])q.pop_b(); q.push(i); if(f[d-1][q.front()][j]>-1) f[d][i][j]=f[d-1][q.front()][j]+i-q.front(); ans=max(ans,f[d][i][j]); } } } else if(fx==3){ _for(i,1,n){ dq q;q.nw(); for_(j,m,1){ if(mp[i][j]){ while(!q.empty())q.pop_f(); continue; } while(!q.empty()&&q.front()>j+len)q.pop_f(); while(!q.empty()&&f[d-1][i][q.back()]+q.back()-j<f[d-1][i][j])q.pop_b(); q.push(j); if(f[d-1][i][q.front()]>-1) f[d][i][j]=f[d-1][i][q.front()]+q.front()-j; ans=max(ans,f[d][i][j]); } } } else{ _for(i,1,n){ dq q;q.nw(); _for(j,1,m){ if(mp[i][j]){ while(!q.empty())q.pop_f(); continue; } while(!q.empty()&&q.front()<j-len)q.pop_f(); while(!q.empty()&&f[d-1][i][q.back()]+j-q.back()<f[d-1][i][j])q.pop_b(); q.push(j); if(f[d-1][i][q.front()]>-1) f[d][i][j]=f[d-1][i][q.front()]+j-q.front(); ans=max(ans,f[d][i][j]); } } } } int main(){ n=rnt(),m=rnt(),x=rnt(),y=rnt(),k=rnt(); _for(i,1,n) _for(j,1,m) mp[i][j]=(bool)(rch()=='x'); memset(f,-inf,sizeof(f)); f[0][x][y]=0; _for(i,1,k){ int s=rnt(),t=rnt(),fx=rnt(); dp(i,s,t,fx); } printf("%d\n",ans); return 0; }
一共有 \(T\) 天,知道了每天股票的买入金额 \(ap_i\),卖出金额 \(bp_i\),买入限制 \(as_j\),卖出限制 \(bs_j\)。要求持有股票数不能超过 \(MaxP\) ,两次交易相隔 \(w\) 天。
\(1\le w<T\le 2*10^3,1\le MaxP\le 2*10^3\)
设 \(f_{i,j}\) 表示第 \(i\) 天持有 \(j\) 个股票的最大钱数。
有四种情况:
此时时间复杂度是 \(O(T*MaxP^2)\)。
观察只买和只卖的情况,可以发现它们的转移方程可以用单调队列优化掉一维。
那么时间复杂度就被优化成了 \(O(T*MaxP)\),完全可过。
#include<bits/stdc++.h> #define _for(i,a,b) for(ll i=a;i<=b;++i) #define for_(i,a,b) for(ll i=a;i>=b;--i) #define ll long long using namespace std; const ll N=4010,inf=0x3f3f3f3f; ll t,mxp,w,ap[N],bp[N],as[N],bs[N]; inline ll rnt(){ ll x=0,w=1;char c=getchar(); while(c<'0'||c>'9'){if(c=='-')w=-1;c=getchar();} while(c>='0'&&c<='9')x=(x<<3)+(x<<1)+(c^48),c=getchar(); return x*w; } struct dq{//deque ll q[N],h,t; void nw(){ memset(q,0,sizeof(q)); h=1,t=0; } bool empty(){return h>t;} ll front(){return q[h];} ll back(){return q[t];} void pop_f(){++h;} void pop_b(){--t;} void push(int x){q[++t]=x;} }; namespace SOLVE{ ll f[N][N]; void DpPkm(ll i){//凭空买 _for(j,0,min(mxp,as[i])) f[i][j]=-ap[i]*j; return; } void DpBmbm(ll i){//不买也不卖 _for(j,0,mxp) f[i][j]=max(f[i][j],f[i-1][j]); return; } void DpBuy(ll i){//原基础上买来 dq q;q.nw(); _for(j,0,mxp){ while(!q.empty()&&j-q.front()>as[i])q.pop_f(); while(!q.empty()&&f[i-w-1][j]>f[i-w-1][q.back()]-(j-q.back())*ap[i])q.pop_b(); q.push(j); f[i][j]=max(f[i][j],f[i-w-1][q.front()]-(j-q.front())*ap[i]); } return; } void DpSell(ll i){//原基础上卖出 dq q;q.nw(); for_(j,mxp,0){ while(!q.empty()&&q.front()-j>bs[i])q.pop_f(); while(!q.empty()&&f[i-w-1][j]>f[i-w-1][q.back()]+(q.back()-j)*bp[i])q.pop_b(); q.push(j); f[i][j]=max(f[i][j],f[i-w-1][q.front()]+(q.front()-j)*bp[i]); } return; } ll Solve(){ memset(f,-inf,sizeof(f)); _for(i,1,t){ DpPkm(i); DpBmbm(i); if(i-w>1){ DpBuy(i); DpSell(i); } } return f[t][0]; } } int main(){ t=rnt(),mxp=rnt(),w=rnt(); _for(i,1,t) ap[i]=rnt(),bp[i]=rnt(),as[i]=rnt(),bs[i]=rnt(); printf("%lld\n",SOLVE::Solve()); return 0; }/* */