[BZOJ 1499][NOI 2005]瑰丽华尔兹(DP+单调队列优化)

题目链接

http://www.lydsy.com/JudgeOnline/problem.php?id=1499

思路

一个很显然的 O(nmT) 的做法就是令 f[t][i][j] 表示时刻 t 时,钢琴位于 (i,j) 处时,从时刻 1 t 的最长滑行路程。很容易得到DP方程

f[t][i][j]=max{f[t1][i][j],f[t1][ilast][jlast]+1}

这样做显然太慢,由于钢琴的移动方向是在一段一段的时间内是相同的,因此可以考虑成段DP。令 f[t][i][j] 表示第 t 段时间时,钢琴位于 (i,j) 处时,从第 1 段时间到第 t 段时间的最长滑行路程。
f[t][i][j]=max{f[t1][ilast][jlast]+dist{(ilast,jlast),(i,j)}}

下面举一个简单例子,当前这段时间内移动方向都是朝右,来讲讲如何利用单调队列优化
可以列出DP方程
f[t][i][j]=max{f[t1][i][j]+(jj)}=j+max{f[t1][i][j]j},jjstarttendt+1

t 段移动方向对应于时间段 [startt,endt]

j 是个常量,因此我们就是要找到最大的 max{f[t1][i][j]j} ,我们可以维护一个单调队列,保存 t1 时刻的所有可行状态的 f 值与 j ,保证 j 单调递增, f 值单调递减,在dp到第 t 段时,首先枚举行 i ,然后从左到右来枚举 j ,并时刻保证队首的 j 满足限制条件 jjstarttendt+1 ,这样的单调队列的队首显然是最优的,用此时的队首的 f[t1][i][j] 来更新答案。

其他三个方向的DP过程也类似,这样做最终的时间复杂度是 O(nmk)

代码

#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <algorithm>
#include <queue>

#define MAXN 1000
#define MAXK 300
#define INF 0x3f3f3f3f

using namespace std;

typedef pair<int,int>pr;

int xx[]={-1,1,0,0},yy[]={0,0,-1,1};

int n,m,K,ans,cur;
int sx,sy;
bool mp[MAXN][MAXN];
int f[2][MAXN][MAXN];

struct Segment
{
    int st,ed,dir;
}seg[MAXN];

bool operator<(Segment a,Segment b)
{
    return a.st<b.st;
}

struct Monoque
{
    deque<pr>q;
    void clear()
    {
        q.clear();
    }
    void Insert(int pos,int val)
    {
        while(!q.empty()&&q.back().second<val) q.pop_back();
        q.push_back(make_pair(pos,val));
    }
    int query(int pos,int limit)
    {
        while(!q.empty()&&pos-q.front().first>limit) q.pop_front();
        return q.front().second;
    }
}monoq;

void update(int time,int cur,int x,int y,int t) //第t段状态区间
{
    if(mp[x][y]) //(x,y)是障碍物
    {
        f[cur][x][y]=-INF;
        monoq.clear();
        return;
    }
    monoq.Insert(time,f[cur^1][x][y]-time);
    f[cur][x][y]=monoq.query(time,seg[t].ed-seg[t].st+1)+time;
    ans=max(ans,f[cur][x][y]);
}

int main()
{
    scanf("%d%d%d%d%d",&n,&m,&sx,&sy,&K);
    char s[MAXN];
    for(int i=1;i<=n;i++)
    {
        scanf("%s",s+1);
        for(int j=1;j<=m;j++)
        {
            mp[i][j]=s[j]=='x';
            f[0][i][j]=-INF;
        }
    }
    f[0][sx][sy]=0;
    for(int i=1;i<=K;i++)
        scanf("%d%d%d",&seg[i].st,&seg[i].ed,&seg[i].dir);
    sort(seg+1,seg+K+1);
    for(int i=1;i<=K;i++)
    {
        cur^=1;
        if(seg[i].dir==1) //上
            for(int y=1;y<=m;y++)
            {
                monoq.clear();
                for(int x=n;x>=1;x--)
                    update(n-x,cur,x,y,i);
            }
        else if(seg[i].dir==2) //下
            for(int y=1;y<=m;y++)
            {
                monoq.clear();
                for(int x=1;x<=n;x++)
                    update(x,cur,x,y,i);
            }
        else if(seg[i].dir==3) //左
            for(int x=1;x<=n;x++)
            {
                monoq.clear();
                for(int y=m;y>=1;y--)
                    update(m-y,cur,x,y,i);
            }
        else
            for(int x=1;x<=n;x++)
            {
                monoq.clear();
                for(int y=1;y<=m;y++)
                    update(y,cur,x,y,i);
            }
    }
    printf("%d\n",ans);
    return 0;
}

你可能感兴趣的:([BZOJ 1499][NOI 2005]瑰丽华尔兹(DP+单调队列优化))