POJ - 3926 Parade(单调队列优化dp)
題目鏈接:點(diǎn)擊查看
題目大意:給出一個(gè)n*m的街道,其中有(n+1)*m條街道,每條街道都有一個(gè)值,現(xiàn)在我們需要從最下面的任意一點(diǎn)出發(fā),到達(dá)最上面的任意一點(diǎn)結(jié)束,問如何規(guī)劃路線能讓沿途經(jīng)過的街道的權(quán)值和最大,現(xiàn)在多了一個(gè)規(guī)則,就是橫向的街道每次最多只能走k個(gè)單位的距離
題目分析:首先我們?cè)谧x入數(shù)據(jù)的時(shí)候可以將行反轉(zhuǎn)一下,這樣題目就轉(zhuǎn)換成了從第一行任意一點(diǎn)出發(fā),到最后一行任意一點(diǎn)結(jié)束,所能產(chǎn)生的最大貢獻(xiàn),這一步可有可無,純粹是習(xí)慣性的方便計(jì)算
然后我們來設(shè)計(jì)一下動(dòng)態(tài)規(guī)劃的dp,轉(zhuǎn)移方程以及初始條件,因?yàn)槭且粋€(gè)二維的坐標(biāo)系,我們可以設(shè)dp[i][j]代表到達(dá)第i行第j列所能產(chǎn)生的最大貢獻(xiàn),這個(gè)時(shí)候轉(zhuǎn)移方程也就很輕易的就寫出來了:設(shè)val[i]是1~i的權(quán)值的前綴和
dp[i][j]=max(dp[i-1][j]+val[i][kk]-val[i][j],dp[i-1][j]+val[i][j]-val[i][kkk]);
注意因?yàn)槲覀冊(cè)诠諒澲?#xff0c;只能單方向前進(jìn),所以只有兩種情況,一種是到下一行之后去左邊走距離不超過k的最大權(quán)值,另一種是到下一行之后去右邊走距離不超過k的最大權(quán)值
因?yàn)関al是前綴和,可能有的同學(xué)會(huì)問了,不應(yīng)該是val[i][j]-val[i][kk-1]嘛,確實(shí)正常的前綴和是需要這樣寫的,但這個(gè)題目比較特殊,我們觀察一下給出的樣例以及題目中的圖片可以知道,節(jié)點(diǎn)的個(gè)數(shù)總是比街道的個(gè)數(shù)多一個(gè),換句話說,每條街道都會(huì)被兩個(gè)節(jié)點(diǎn)夾在一起,那么我們不妨在用前綴和的時(shí)候,讓每個(gè)前綴和都代表一個(gè)節(jié)點(diǎn),m個(gè)節(jié)點(diǎn)加上val[0]就剛好組成了m+1個(gè)節(jié)點(diǎn)了,這樣一來在這個(gè)題目中前綴和查詢區(qū)間和就不需要讓左端點(diǎn)減一了
接下來我們的問題是如何快速查找區(qū)間最大值,如果暴力找的話肯定會(huì)T掉,因?yàn)闀r(shí)間復(fù)雜度就變成了n*m*m=1e10,用st表會(huì)爆內(nèi)存,因?yàn)槲矣?jì)算的內(nèi)存是需要80M,而這個(gè)題目只提供了60M,用線段樹也會(huì)被卡常,線段樹每次區(qū)間查詢最值會(huì)有l(wèi)ogn的開銷,這樣時(shí)間復(fù)雜度雖然優(yōu)化成了n*m*logm,但大概還是有1e7的時(shí)間復(fù)雜度,按照理論來說,應(yīng)該是勉強(qiáng)可以過的,但這個(gè)題目時(shí)間卡的很緊,如果用單調(diào)隊(duì)列優(yōu)化之后也還是需要加一個(gè)快讀才能勉強(qiáng)250ms跑過去,所以多出這logm的時(shí)間開銷,至少是需要2s的時(shí)間才能跑,放在這個(gè)題目里,st表無疑會(huì)T掉
話說回來,如果是動(dòng)態(tài)查詢區(qū)間最值,我們就可以用單調(diào)隊(duì)列進(jìn)行優(yōu)化了,時(shí)間復(fù)雜度就可以優(yōu)化為n*m,常數(shù)為2,具體實(shí)現(xiàn)的話,我們就先討論其中的一種情況吧,dp[i][j]=dp[i-1][j]+val[i][kk]-val[i][j],我們可以發(fā)現(xiàn),dp[i-1][j]和val[i][j]的列坐標(biāo)是綁定在一起的,所以我們應(yīng)該將其視為一個(gè)整體,對(duì)這個(gè)整體維護(hù)一個(gè)非嚴(yán)格遞減的單調(diào)隊(duì)列,這樣在滿足條件的區(qū)間內(nèi),我們選取最大值來更新dp[i][j]即可,對(duì)于kk<=j的情況我們可以這樣從0~m遍歷一遍更新,對(duì)于kk>=j的情況我們就從m~0反著遍歷一遍并更新一下答案就可以了,但我們會(huì)發(fā)現(xiàn),j=kk的這個(gè)點(diǎn)被更新了兩次,其實(shí)在這個(gè)題目中只是增加了O(1)的時(shí)間開銷,并不會(huì)影響答案的正確性,所以不用特殊處理了
另外我是今天才知道deque這個(gè)stl容器并不是C++11才有的。。所以直接用deque模擬單調(diào)隊(duì)列會(huì)方便很多,這個(gè)題目卡了快讀的時(shí)間,卻沒有卡stl的時(shí)間,有點(diǎn)無語(yǔ)。。
對(duì)了記得枚舉每一列m的時(shí)候,要從0-m,這個(gè)上文中已經(jīng)提過了,我們是將前綴和分為了m+1個(gè)節(jié)點(diǎn)分別代表著每個(gè)頂點(diǎn),兩兩頂點(diǎn)之間的才代表著相應(yīng)的道路
代碼:
deque模擬單調(diào)隊(duì)列:
#include<iostream> #include<cstdlib> #include<string> #include<cstring> #include<cstdio> #include<algorithm> #include<climits> #include<cmath> #include<cctype> #include<stack> #include<queue> #include<list> #include<vector> #include<set> #include<map> #include<sstream> #include<deque> using namespace std;typedef long long LL;const int inf=0x3f3f3f3f;const int N=110;template<class T>inline void read(T &res) {char c;T flag=1;while((c=getchar())<'0'||c>'9')if(c=='-')flag=-1;res=c-'0';while((c=getchar())>='0'&&c<='9')res=res*10+c-'0';res*=flag; }struct Node {int id,val;Node(int ID,int VAL){id=ID;val=VAL;} };int head,tail;int dp[N][N*N],val[N][N*N],len[N][N*N];void init() {memset(dp,-inf,sizeof(dp));memset(dp[0],0,sizeof(dp[0])); }int main() { // freopen("input.txt","r",stdin);int n,m,k;while(scanf("%d%d%d",&n,&m,&k)!=EOF&&n+m+k){init();for(int i=n+1;i>=1;i--)for(int j=1;j<=m;j++){read(val[i][j]);val[i][j]+=val[i][j-1];}for(int i=n+1;i>=1;i--)for(int j=1;j<=m;j++){read(len[i][j]);len[i][j]+=len[i][j-1];}for(int i=1;i<=n+1;i++){deque<Node>q;for(int j=0;j<=m;j++)//dp[i][j]=dp[i-1][j]+val[i][j]-val[i][kk]{int temp=dp[i-1][j]-val[i][j];while(q.size()&&q.back().val<temp)q.pop_back();q.push_back(Node(j,temp));while(q.size()&&len[i][j]-len[i][q.front().id]>k)q.pop_front();dp[i][j]=max(dp[i][j],q.front().val+val[i][j]);}q.clear();for(int j=m;j>=0;j--)//dp[i][j]=dp[i-1][j]+val[i][kk]-val[i][j]{int temp=dp[i-1][j]+val[i][j];while(q.size()&&q.back().val<temp)q.pop_back();q.push_back(Node(j,temp));while(q.size()&&len[i][q.front().id]-len[i][j]>k)q.pop_front();dp[i][j]=max(dp[i][j],q.front().val-val[i][j]);}}int ans=-inf;for(int i=1;i<=m;i++)ans=max(ans,dp[n+1][i]);printf("%d\n",ans);}return 0; }數(shù)組模擬單調(diào)隊(duì)列:
#include<iostream> #include<cstdlib> #include<string> #include<cstring> #include<cstdio> #include<algorithm> #include<climits> #include<cmath> #include<cctype> #include<stack> #include<queue> #include<list> #include<vector> #include<set> #include<map> #include<sstream> using namespace std;typedef long long LL;const int inf=0x3f3f3f3f;const int N=110;template<class T>inline void read(T &res) {char c;T flag=1;while((c=getchar())<'0'||c>'9')if(c=='-')flag=-1;res=c-'0';while((c=getchar())>='0'&&c<='9')res=res*10+c-'0';res*=flag; }struct Queue {int id,val; }q[N*N];int head,tail;int dp[N][N*N],val[N][N*N],len[N][N*N];void init() {memset(dp,-inf,sizeof(dp));memset(dp[0],0,sizeof(dp[0])); }int main() { // freopen("input.txt","r",stdin);int n,m,k;while(scanf("%d%d%d",&n,&m,&k)!=EOF&&n+m+k){init();for(int i=n+1;i>=1;i--)for(int j=1;j<=m;j++){read(val[i][j]);val[i][j]+=val[i][j-1];}for(int i=n+1;i>=1;i--)for(int j=1;j<=m;j++){read(len[i][j]);len[i][j]+=len[i][j-1];}for(int i=1;i<=n+1;i++){head=0;tail=-1;for(int j=0;j<=m;j++)//dp[i][j]=dp[i-1][j]+val[i][j]-val[i][kk]{int temp=dp[i-1][j]-val[i][j];while(head<=tail&&q[tail].val<temp)tail--;q[++tail].id=j;q[tail].val=temp;while(head<tail&&len[i][j]-len[i][q[head].id]>k)head++;dp[i][j]=max(dp[i][j],q[head].val+val[i][j]);}head=0;tail=-1;for(int j=m;j>=0;j--)//dp[i][j]=dp[i-1][j]+val[i][kk]-val[i][j]{int temp=dp[i-1][j]+val[i][j];while(head<=tail&&q[tail].val<temp)tail--;q[++tail].id=j;q[tail].val=temp;while(head<=tail&&len[i][q[head].id]-len[i][j]>k)head++;dp[i][j]=max(dp[i][j],q[head].val-val[i][j]);}}int ans=-inf;for(int i=1;i<=m;i++)ans=max(ans,dp[n+1][i]);printf("%d\n",ans);}return 0; }?
總結(jié)
以上是生活随笔為你收集整理的POJ - 3926 Parade(单调队列优化dp)的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: POJ - 2201 Cartesian
- 下一篇: 牛客 - 「土」巨石滚滚(贪心)