單調隊列總結
前言
單調隊列易於理解,這里不多說實現了,只說一些例題和用途
單調隊列實質是O(n)求一段序列中多段有相同長度限制的子序列的最值
裸體裸題:
1.滑動窗口:板子
2.理想正方形 :(其實也是板子)每一橫行用維護單調隊列維護,稱為q1,再用另一組單調隊列維護一列,稱為q2,q2內元素為q1隊首
3.修築綠化帶:與上一題目差別不大,先預處理出以x,y為右下角的C*D矩陣的和,記作d[ i ][ j ],然后在枚舉A*B矩陣右下角,在(A-2)*(B-2)內部單調隊列取d[ i ][ j ]最值,邊界有點惡心
優化DP:
題目概述:你有無限的本金,你要炒股(我也不知道為啥
每天股票買入為 APi,賣出價為BPi,每天最多買ASi股,最多賣BSi股,每次操作后(買入賣出均算操作)需隔w天進行下次操作(會累的嘛),你手里股票不能超過MAXP股,求T天后的最大收益。
考慮暴力DP:
f[ i ][ j ]為到第i天手里有j股的最大收益;
1.憑空買入 f[ i ][ j ]= -APi * j (j <= ASi );
2.摸魚(啥也不干)f[ i ][ j ] = max(f[ i ][ j ] ,f[ i-1 ][ j ]);
3.從以前買/賣 考慮到上一步,我們已經把第i天之前的最優解轉移到第i天了,所以我們直接取f[ i-w-1 ][ j ]進行操作一定是最優的
看到這里暴力DP您一定會寫了QAQ
考慮單調隊列優化:
我們發現第三步的轉移方程是O(n^2)級別的,外面再套個天數就是N^3了,TLE;
列出第三步的買入方程:
f[ i ][ j ] = max{f[ i ][ j ],f[ i-w-1 ][ j-k ] - k*APi}(0<k<=ASi);
略作轉化,將(j-k)設為 k,則方程變為
f[ i ][ j ] = max {f[ i-w-1 ][ k ] + k*APi - j*APi}(j-ASi<=k< j);
將 j*APi 提出
f[ i ][ j ] = max {f[ i-w-1 ][ k ] + k*APi }- j*APi(j-ASi<=k< j);
我們發現問題變為了求 [ j-ASi,j)這段區間內 f[ i-w-1 ][ k ] + k*APi 的最大值,可以用單調隊列優化
買入操作類似,不推了(懶
#include<bits/stdc++.h>
using namespace std; inline int read() { int x=0,f=1; char ch; for(ch=getchar();(ch<'0'||ch>'9')&&ch!='-';ch=getchar()); if(ch=='-') f=0,ch=getchar(); while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+ch-'0';ch=getchar();} return f?x:-x; } int n,m,w,ans; struct point { int ap,bp,as,bs; }g[2010]; int f[2010][2010]; int q[2010][2]; int head,tail; signed main() { n=read(),m=read(),w=read(); memset(f,-0x3f,sizeof(f)); for(int i=1;i<=n;++i) { g[i].ap=read(),g[i].bp=read(),g[i].as=read(),g[i].bs=read(); f[i][0]=0; } for(int i=1;i<=n;++i) { for(int j=1;j<=g[i].as;++j) f[i][j]=-j*g[i].ap; for(int j=0;j<=m;++j) f[i][j]=max(f[i][j],f[i-1][j]); head=0,tail=-1; if(i<=w) continue; for(int j=0;j<=m;++j) { while(q[head][0]<j-g[i].as&&head<=tail) ++head; while(f[i-w-1][j]+j*g[i].ap>=q[tail][1]&&head<=tail) --tail; q[++tail][0]=j; q[tail][1]=f[i-w-1][j]+j*g[i].ap; if(head<=tail) f[i][j]=max(f[i][j],q[head][1]-j*g[i].ap); } head=0,tail=-1; for(int j=m;j>=0;--j) { while(q[head][0]>j+g[i].bs&&head<=tail) ++head; while(f[i-w-1][j]+j*g[i].bp>=q[tail][1]&&head<=tail) --tail; q[++tail][0]=j; q[tail][1]=f[i-w-1][j]+j*g[i].bp; if(head<=tail) f[i][j]=max(f[i][j],q[head][1]-j*g[i].bp); } for(int j=0;j<=m;++j) ans=max(ans,f[i][j]); } printf("%d\n",ans); return 0; }
瑰麗華爾茲
題目概述:在n*m網格上有個人,初始在(x,y)點,K次輸入,每次給出st,ed,和d,他在st到ed時間段內每秒可以朝着d方向走一格或不走,地圖上有一些障礙,人不能碰到障礙或走出地圖,請問這個人最后最多走多少格;
暴力DP:
設f[ t ][ i ][ j ]為在第t次給出后在(i,j)處最多走了多少格
每次給出st,ed,和 d 后循環每個節點,直接在每個節點模擬
復雜度O(K*N*M*T);
加個滾動數組優化你A了(?)
本着嚴謹的態度我們還是談一下單調隊列優化:
令ed=ed-st+1;
繼續枚舉(i,j),問題為在(i,j)處從某一距離不超過ed方向轉移過來的最大值,符合單調隊列本質
#include<bits/stdc++.h>
using namespace std; inline int read() { int x=0,f=1; char ch; for(ch=getchar();(ch<'0'||ch>'9')&&ch!='-';ch=getchar()); if(ch=='-') f=0,ch=getchar(); while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+ch-'0';ch=getchar();} return f?x:-x; } int n,m,x,y,k,ans; int a[210][210]; char ch; int f[2][210][210]; int t,tx,ty,st,ed,d; int dx[]={0,1,-1,0,0}; int dy[]={0,0,0,1,-1}; int q[210][2]; int head,tail; inline void work1(int y) { for(int x=n+1;x>=1;--x) { if(!a[x][y]) { head=0,tail=-1; continue; } while(q[head][0]-x>ed&&head<=tail) ++head; while(f[t^1][x][y]+x>=q[tail][1]&&head<=tail) --tail; q[++tail][0]=x; q[tail][1]=f[t^1][x][y]+x; f[t][x][y]=max(f[t][x][y],q[head][1]-x); } } inline void work2(int y) { for(int x=0;x<=n;++x) { if(!a[x][y]) { head=0,tail=-1; continue; } while(x-q[head][0]>ed&&head<=tail) ++head; while(f[t^1][x][y]-x>=q[tail][1]&&head<=tail) --tail; q[++tail][0]=x; q[tail][1]=f[t^1][x][y]-x; f[t][x][y]=max(f[t][x][y],q[head][1]+x); } } inline void work3(int x) { for(int y=m+1;y>=1;--y) { if(!a[x][y]) { head=0,tail=-1; continue; } while(q[head][0]-y>ed&&head<=tail) ++head; while(f[t^1][x][y]+y>=q[tail][1]&&head<=tail) --tail; q[++tail][0]=y; q[tail][1]=f[t^1][x][y]+y; f[t][x][y]=max(f[t][x][y],q[head][1]-y); } } inline void work4(int x) { for(int y=0;y<=m;++y) { if(!a[x][y]) { head=0,tail=-1; continue; } while(y-q[head][0]>ed&&head<=tail) ++head; while(f[t^1][x][y]-y>=q[tail][1]&&head<=tail) --tail; q[++tail][0]=y; q[tail][1]=f[t^1][x][y]-y; f[t][x][y]=max(f[t][x][y],q[head][1]+y); } } signed main() { n=read(),m=read(),x=read(),y=read(),k=read(); for(int i=1;i<=n;++i) { for(int j=1;j<=m;++j) { for(ch=getchar();(ch!='.'&&ch!='x');ch=getchar()); a[i][j]=(ch=='.'); } } memset(f,-0x3f,sizeof(f)); f[t][x][y]=0; for(int p=1;p<=k;++p) { st=read(),ed=read(),d=read(); ed=ed-st+1; t^=1; if(d==1)//北
for(int j=1;j<=m;++j) work1(j); if(d==2)//南
for(int j=1;j<=m;++j) work2(j); if(d==3)//西
for(int i=1;i<=n;++i) work3(i); if(d==4) for(int i=1;i<=n;++i) work4(i); } for(int x=1;x<=n;++x) for(int y=1;y<=m;++y) ans=max(ans,f[t][x][y]); printf("%d\n",ans); return 0; }