bzoj1499 [NOI2005]瑰丽华尔兹 (单调队列优化DP)

来源:互联网 发布:好易网络电视apk 编辑:程序博客网 时间:2024/06/05 06:09

bzoj1499 [NOI2005]瑰丽华尔兹

原题地址:http://www.lydsy.com/JudgeOnline/problem.php?id=1499

题意:
舞厅是一个N行M列的矩阵,一些地方有障碍物,其他的则是空地。钢琴初始在(x,y),可以在空地上滑动,但不能撞上家具或滑出舞厅。每个时刻,钢琴都会倾斜的方向向相邻的方格滑动一格,相邻的方格可以是向东、向西、向南或向北的。
每个时刻可以让钢琴滑动或原地不动。给出每段时间的倾斜情况,钢琴最长的滑行路程。

数据范围
N,M,K <=200

(船体倾斜情况是按时间的区间来描述的,且从1开始计量时间,比如“在[1, 3]时间里向东倾斜,[4, 5]时间里向北倾斜”。因此K表示区间的数目,顺序描述K个时间区间,格式为:si ti di。表示在时间区间[si, ti]内,船体都是向di方向倾斜的。di为1, 2, 3, 4中的一个,依次表示北、南、西、东(分别对应矩阵中的上、下、左、右)。输入保证区间是连续的,即 s1 = 1 si = ti-1 + 1 (1 < i ≤ K) tK = T)

题解:
最开始能够想到的DP是对于每个时间点每个位置,但是肯定是会T的。
K的范围<=200,而在一个段内只能往同一个方向滑动,可以想到按段来DP。
此时有:(以向右倾斜为例)
dp[i][j][k] = max(dp[i][j’]+j-j’) (j-j’<=t-s+1且(i,j)与(i,j’)之间没有障碍)
这样的复杂度是O(n^2 M K)

现在考虑优化,要做到每次只取一次就能取出最大的dp[i][j’]+j-j’
显然对于每个j,dp[i][j’]+j-j’在变化,于是移项得:
dp[i][j][k] = max(dp[i][j’][k-1]+j-j’)
dp[i][j][k]-j = max(dp[i][j’][k-1]-j’)
转化为j,j’各在一边的情况,可以用单调队列来维护这个dp[i][j][k]-j ,每次取队首即可。

但是对于四种情况都要跑单调队列DP感觉还是非常繁琐,似乎要百行以上。
看了这位dalao的博客
把四种情况合起来写,就非常清爽了。
尤其是为了计算j-j’这个差值,因为并没有标明是行还是列,直接用一个累加器累加也是很巧的简化方式。

代码:

#include<cstdio>#include<iostream>#include<algorithm>#include<cstring>#include<queue>using namespace std;const int N=210;struct node{    int a,b;    node(){}    node(int a,int b):a(a),b(b){}}Q[N];//1 上  2下  3左   4右 int n,m,sx,sy,k,dp[N][N];int fx[5]={0,-1,1,0,},fy[5]={0,0,0,-1,1},ans=0;char s[N][N];void solve(int opt,int x,int y,int t){    int lf=0,rg=0;    for(int i=0;x>=1&&x<=n&&y>=1&&y<=m;x+=fx[opt],y+=fy[opt],i++)    {           if(s[x][y]=='x') {lf=rg=0; continue;}        int a=dp[x][y]; int b=i;        while(lf<rg&&Q[rg].a-Q[rg].b<=a-b) rg--;        Q[++rg].a=a; Q[rg].b=b;        while(lf<rg&&(i-Q[lf+1].b)>t) lf++;         dp[x][y]=Q[lf+1].a-Q[lf+1].b+i;        ans=max(ans,dp[x][y]);      }}int main(){       scanf("%d%d%d%d%d",&n,&m,&sx,&sy,&k);    for(int i=1;i<=n;i++)    scanf("%s",s[i]+1);    memset(dp,-60,sizeof(dp));    dp[sx][sy]=0;    for(int i=1;i<=k;i++)    {        int l,r,opt;        scanf("%d%d%d",&l,&r,&opt);        if(opt==1) for(int j=1;j<=m;j++) solve(1,n,j,r-l+1);        else        if(opt==2) for(int j=1;j<=m;j++) solve(2,1,j,r-l+1);        else        if(opt==3) for(int j=1;j<=n;j++) solve(3,j,m,r-l+1);        else for(int j=1;j<=n;j++) solve(4,j,1,r-l+1);    }    printf("%d\n",ans);    return 0;}
阅读全文
1 0
原创粉丝点击