BZOJ1499 單調隊列+DP
1499: [NOI2005]瑰麗華爾茲
Time Limit: 3 Sec Memory Limit: 64 MBSubmit: 1560 Solved: 949
[Submit][Status][Discuss]
Description
你跳過華爾茲嗎?當音樂響起,當你隨著旋律滑動舞步,是不是有一種漫步仙境的愜意?眾所周知,跳華爾茲時,最重要的是有好的音樂。但是很少有幾個人知道,世界上最偉大的鋼琴家一生都漂泊在大海上,他的名字叫丹尼?布德曼?T.D.?檸檬?1900,朋友們都叫他1900。 1900在20世紀的第一年出生在往返於歐美的郵輪弗吉尼亞號上,很不幸他剛出生就被拋棄了,成了孤兒。1900孤獨的成長在弗吉尼亞號上,從未離開過這個搖晃的世界。也許是對他命運的補償,上帝派可愛的小天使艾米麗照顧他。可能是天使的點化,1900擁有不可思議的鋼琴天賦:從未有人教,從沒看過樂譜,但他卻能憑著自己的感覺彈出最沁人心脾的旋律。當1900的音樂獲得郵輪上所有人的歡迎時,他才8歲,而此時的他已經乘著海輪往返歐美大陸50余次了。雖說是鋼琴奇才,但1900還是個孩子,他有著和一般男孩一樣的好奇和調皮,只不過更多一層浪漫的色彩罷了:這是一個風雨交加的夜晚,海風卷起層層巨浪拍打著弗吉尼亞號,郵輪隨著巨浪劇烈的搖擺。船上的新薩克斯手馬克斯?托尼暈船了,1900招呼托尼和他一起坐上舞廳裏的鋼琴,然後松開了固定鋼琴的閘,於是,鋼琴隨著海輪的傾斜滑動起來。準確的說,我們的主角1900、鋼琴、郵輪隨著1900的旋律一起跳起了華爾茲,隨著“嘣嚓嚓”的節奏,托尼的暈船癥也奇跡般的消失了。後來托尼在回憶錄上寫道:大海搖晃著我們使我們轉來轉去快速的掠過燈和家具我意識到我們正在和大海一起跳舞真是完美而瘋狂的舞者晚上在金色的地板上快樂的跳著華爾茲是不是很愜意呢?也許,我們忘記了一個人,那就是艾米麗,她可沒閑著:她必須在適當的時候施展魔法幫助1900,不讓鋼琴碰上舞廳裏的家具。不妨認為舞廳是一個N行M列的矩陣,矩陣中的某些方格上堆放了一些家具,其他的則是空地。鋼琴可以在空地上滑動,但不能撞上家具或滑出舞廳,否則會損壞鋼琴和家具,引來難纏的船長。每個時刻,鋼琴都會隨著船體傾斜的方向向相鄰的方格滑動一格,相鄰的方格可以是向東、向西、向南或向北的。而艾米麗可以選擇施魔法或不施魔法:如果不施魔法,則鋼琴會滑動;如果施魔法,則鋼琴會原地不動。艾米麗是個天使,她知道每段時間的船體的傾斜情況。她想使鋼琴在舞廳裏滑行路程盡量長,這樣1900會非常高興,同時也有利於治療托尼的暈船。但艾米麗還太小,不會算,所以希望你能幫助她。
Input
輸入文件的第一行包含5個數N, M, x, y和K。N和M描述舞廳的大小,x和y為鋼琴的初始位置(x行y列);我們對船體傾斜情況是按時間的區間來描述的,且從1開始計量時間,比如“在[1, 3]時間裏向東傾斜,[4, 5]時間裏向北傾斜”,因此這裏的K表示區間的數目。以下N行,每行M個字符,描述舞廳裏的家具。第i行第j列的字符若為‘ . ’,則表示該位置是空地;若為‘ x ’,則表示有家具。以下K行,順序描述K個時間區間,格式為:si ti di。表示在時間區間[si, ti]內,船體都是向di方向傾斜的。di為1, 2, 3, 4中的一個,依次表示北、南、西、東(分別對應矩陣中的上、下、左、右)。輸入保證區間是連續的,即 s1 = 1 si = ti-1 + 1 (1 < i ≤ K) tK = T
Output
輸出文件僅有1行,包含一個整數,表示鋼琴滑行的最長距離(即格子數)。
Sample Input
4 5 4 1 3..xx.
.....
...x.
.....
1 3 4
4 5 1
6 7 3
Sample Output
6HINT
分析:
令 f(k,x,y)=此人 k 次滑行後到達(x,y)方格時已經滑行的最長距離。動態規劃的狀態轉移方程如下(以下僅給出向東滑行的狀態轉移方程,其他 3 個方向上的轉移方程可以 類似地推出):f(0,startx,starty)=0,f(k,x,y)=max{f(k-1,x,y),f(k-1,x,y-1)+1,f(k-1,x,y-2)+2,......,f(k-1,x,y’)+y-y’} (其中 y’為滿足 y=1 或(x,y’-1)上有障礙或 y’=y-ck 的最大值),
對於一個具體的例子 k=2,x=1,c2=2 可以列出如下等式: f(2,1,1)=max{f(1,1,1)}
f(2,1,2)=max{f(1,1,1)+1,f(1,1,2)}
f(2,1,3)=max{f(1,1,1)+2,f(1,1,2)+1,f(1,1,3)}
f(2,1,4)=max{f(1,1,2)+2,f(1,1,3)+1,f(1,1,4)}
......
如果我們定義一個序列 a,使得 ai=f(1,1,i)-i+1,則以上等式可以寫成:
f(2,1,1)=max{a1}=max{a1}
f(2,1,2)=max{a1+1,a2+1}=max{a1,a2}+1
f(2,1,3)=max{a1+2,a2+2,a3+2}=max{a1,a2,a3}+2
f(2,1,4)=max{a1+3,a2+3,a3+3,a4+3}=max{a2,a3,a4}+3
......
顯然,在應用了 a 序列之後,我們就可以只關註 a 序列而不必為每個 ai 加上一個不
同的值,從而簡化了操作。
前面已經對於序列的插入與刪除進行過討論。其中只在一端插入,另一端刪除的特 性恰好符合隊列的性質。但是,這裏是要求隊列中所有數的最大值,普通的隊列可以勝 任這個操作嗎?讓我們首先來分析一下如何存儲隊列中的數。
如圖 16 所示,對於已經出現在隊列中的 a2 與 a3,如果 a2≤a3,則 a2 是沒有必要出 現在隊列中的。因為根據隊列的插入與刪除原則可以推導出,如果隊列中已經出現 a3 了,則在 a2 被刪除之前,a3 是一定不會被刪除的。因此,a2 與 a3 會一直同時出現在隊 列中,直至 a2 被刪除。但是 a2≤a3,因此隊列中的最大值永遠不會是 a2,也就沒有必要 存儲 a2.
根據這一條重要的性質,可以立刻推導出“有必要”存儲在隊列中的 a 值的大小關 系——嚴格遞減。也就是說,存儲在隊列中的 a 值是依次減小的,而隊頭元素的值為最 大值,也就是當前隊列中所有數字(無論是否存儲在隊列中)的最大值。這樣,每次可 以取出位於隊頭的 a 值作為最大值。但是一個新的問題擺在我們面前——如何實時維護 隊列,即如何正確地插入或刪除元素。
首先研究刪除操作。很顯然,如果隊頭 a 值的下標對應的方格與當前處理的方格之 間的距離已經大於 ck,則直接將它從隊列中刪除,即隊頭指針加一。
接著是插入操作。根據前文中對於隊列中元素大小關系的討論可以得知,插入一個 元素 ai 後,隊列中不能有元素 aj 滿足 aj≤ai. 於是,我們可以從隊尾開始,依次刪除掉不 大於 ai 的 a 值,直到隊列中剩下的元素都大於 ai. 此時就可以將 ai 插入隊尾。(插入與 刪除過程見圖 17)。
很顯然,每次求隊列中所有元素的最大值可以直接查看隊頭元素。根據此隊列性質, 隊頭元素一定是隊列中所有元素裏最大的一個。
1 #include "iostream" 2 #include "cstdio" 3 #include "cstring" 4 #include "string" 5 #include "algorithm" 6 using namespace std; 7 const int maxn=200+10; 8 char g[maxn][maxn]; 9 int f[maxn][maxn]; 10 int n,m,x,y,k,l,r,res; 11 pair<int,int> q[maxn],tmp; 12 int dx[]={0,-1,1,0,0},dy[]={0,0,0,-1,1}; 13 bool judge(int x,int y){ 14 if(x>=1&&x<=n&&y>=1&&y<=m) return true; 15 return false; 16 } 17 void solve(int x,int y,int d,int len){ 18 l=r=0; 19 for(int i=0;judge(x,y);i++,x+=dx[d],y+=dy[d]){ 20 if(g[x][y]==‘x‘) l=r=0; 21 else{ 22 tmp.first=f[x][y],tmp.second=i; 23 while(l<r&&q[r-1].first+(i-q[r-1].second)<=tmp.first) r--; 24 q[r++]=tmp; 25 while(l<r&&(i-q[l].second)>len) l++; 26 f[x][y]=q[l].first+(i-q[l].second); 27 res=max(res,f[x][y]); 28 } 29 } 30 } 31 int main() 32 { 33 scanf("%d%d%d%d%d",&n,&m,&x,&y,&k); 34 for(int i=1;i<=n;i++) 35 scanf("%s",g[i]+1); 36 memset(f,0x80,sizeof(f)); f[x][y]=0; 37 for(int i=1,s,e,d,len;i<=k;i++){ 38 scanf("%d%d%d",&s,&e,&d); len=e-s+1; 39 if(d==1) for(int j=1;j<=m;j++) solve(n,j,1,len); 40 else if(d==2) for(int j=1;j<=m;j++) solve(1,j,2,len); 41 else if(d==3) for(int j=1;j<=n;j++) solve(j,m,3,len); 42 else for(int j=1;j<=n;j++) solve(j,1,4,len); 43 } 44 printf("%d\n",res); 45 }View Code
詳見2006朱晨光《基本數據結構在信息學競賽當中的應用》
BZOJ1499 單調隊列+DP