码迷,mamicode.com
首页 > 其他好文 > 详细

BZOJ1499 单调队列+DP

时间:2017-05-10 21:25:53      阅读:194      评论:0      收藏:0      [点我收藏+]

标签:inpu   algo   namespace   ems   iostream   连续   set   problems   ios   

1499: [NOI2005]瑰丽华尔兹

Time Limit: 3 Sec  Memory Limit: 64 MB
Submit: 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

6

HINT

分析:

令 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

标签:inpu   algo   namespace   ems   iostream   连续   set   problems   ios   

原文地址:http://www.cnblogs.com/wolf940509/p/6837959.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!