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

83 篇文章 0 订阅
51 篇文章 1 订阅

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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值