bzoj1499: [NOI2005]瑰丽华尔兹

传送门
一个很显然的O(nmT)的做法就是令f[t][i][j]表示时刻t时,钢琴位于(i,j)处时,从时刻1到t的最长滑行路程。很容易得到DP方程
f[t][i][j]=maxf[t1][i][j],f[t1][ilast][jlast]+1
这样做显然太慢,由于钢琴的移动方向是在一段一段的时间内是相同的,因此可以考虑成段DP。令f[t][i][j]表示第t段时间时,钢琴位于(i,j)处时,从第1段时间到第t段时间的最长滑行路程。
f[t][i][j]=maxf[t1][ilast][jlast]+dist(ilast,jlast),(i,j)
下面举一个简单例子,当前这段时间内移动方向都是朝右,来讲讲如何利用单调队列优化
可以列出DP方程
f[t][i][j]=maxf[t1][i][j']+(jj')=j+maxf[t1][i][j']j',jj'starttendt+1
第t段移动方向对应于时间段[startt,endt]
j是个常量,因此我们就是要找到最大的max{f[t−1][i][j′]−j′},我们可以维护一个单调队列,保存t−1时刻的所有可行状态的f值与j′,保证j′单调递增,f值单调递减,在dp到第t段时,首先枚举行i,然后从左到右来枚举j,并时刻保证队首的j′满足限制条件j−j′≤startt−endt+1,这样的单调队列的队首显然是最优的,用此时的队首的f[t−1][i][j′]来更新答案。
其他三个方向的DP过程也类似,这样做最终的时间复杂度是O(nmk)

#include<iostream>  
#include<cstdio>  
#include<cstring>
#include<algorithm>
#include<cstdlib> 
#define N 210  
using namespace std;
struct abcd{
    int x,y;
}q[N];
char a[N][N];
int f[N][N],g[N][N];
int n,m,k,ans,x,y,z,h,r;
int dis(abcd a,abcd b){return abs(a.x-b.x)+abs(a.y-b.y);}
int ins(abcd x){
    while (h<r&&dis(x,q[r])<=f[x.x][x.y]-f[q[r].x][q[r].y]) r--;
    q[++r]=x;
}
int get(abcd x,int len){
    while (h<r&&dis(q[h+1],x)>len) h++;
    if (r==h) return 0xefefefef;
    return f[q[h+1].x][q[h+1].y]+dis(x,q[h+1]);
}
void up(int len){
    for (int j=1;j<=n;j++){
        r=h=0;
        for (int i=m;i;i--)
            if (a[i][j]=='.'){
                abcd p;
                p.x=i;
                p.y=j;
                g[i][j]=max(f[i][j],get(p,len));
                ins(p);
            }
            else h=r=0;
    }
    memcpy(f,g,sizeof(f));
}
void down(int len){
    for (int j=1;j<=n;j++){
        r=h=0;
        for (int i=1;i<=m;i++)
            if (a[i][j]=='.'){
                abcd p;
                p.x=i;
                p.y=j;
                g[i][j]=max(f[i][j],get(p,len));
                ins(p);
            }
            else h=r=0;
    }
    memcpy(f,g,sizeof(f));
}
void left(int len){
    for (int i=1;i<=m;i++){
        r=h=0;
        for (int j=n;j;j--)
            if (a[i][j]=='.'){
                abcd p;
                p.x=i;
                p.y=j;
                g[i][j]=max(f[i][j],get(p,len));
                ins(p);
            }
            else h=r=0;
    }
    memcpy(f,g,sizeof(f));
}
void right(int len){
    for (int i=1;i<=m;i++){
        r=h=0;
        for (int j=1;j<=n;j++)
            if (a[i][j]=='.'){
                abcd p;
                p.x=i;
                p.y=j;
                g[i][j]=max(f[i][j],get(p,len));
                ins(p);
            }
            else h=r=0;
    }
    memcpy(f,g,sizeof(f));
}
int main(){
    scanf("%d%d%d%d%d",&m,&n,&x,&y,&k);
    for (int i=1;i<=m;i++) scanf("%s",a[i]+1);
    memset(f,0xef,sizeof(f));
    memset(g,0xef,sizeof(g));
    f[x][y]=0;
    for (int i=1;i<=k;i++){
        scanf("%d%d%d",&x,&y,&z);
        if (z==1) up(y-x+1);
        if (z==2) down(y-x+1);
        if (z==3) left(y-x+1);
        if (z==4) right(y-x+1);
    }
    for (int i=1;i<=m;i++)
        for (int j=1;j<=n;j++) ans=max(ans,f[i][j]);
    printf("%d",ans);
} 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值