bzoj1499 [NOI2005]瑰丽华尔兹(luoguP2254)(dp+单调队列)

来源:互联网 发布:python 迭代器 编辑:程序博客网 时间:2024/06/06 03:28

题目链接

分析:
样例图:
这里写图片描述
设计状态f[i][j][k]:表示时间k,正好到(i,j)
知道了刚刚走完的是哪一段,我们就知道了刚才的方向
魔法不限制使用次数,而且不能改变方向(这就很好了嘛)
我们倒着推回去,这样就可以知道上一段时间结束后,我们在哪里了
这样的复杂度是O(nmT)

为了练码力,我专门写了一下这个部分分(50%),发现还是有很多细节的:

#include<cstdio>#include<cstring>#include<iostream>#include<algorithm>using namespace std;int zz[4][2]={{-1,0},{1,0},{0,-1},{0,1}};int f[201][201][201];struct node{    int s,t,z;};node ti[40003];int n,m,x,y,T;bool mp[201][201];int fan(int x){    if (x==0) return 1;    if (x==1) return 0;    if (x==2) return 3;    if (x==3) return 2;}void doit(){    int i,j,k,tt;    memset(f,128,sizeof(f));    f[x][y][1]=0;    int ff=1;    for (i=ti[1].s;i<=ti[1].t;i++)     {        int xx=x+i*zz[ti[1].z][0];        int yy=y+i*zz[ti[1].z][1];        if (mp[xx][yy]&&xx>0&&yy>0&&xx<=n&&yy<=m) f[xx][yy][1]=i;        else break;    }    for (k=2;k<=T;k++)        for (i=1;i<=n;i++)            for (j=1;j<=m;j++)            if (mp[i][j])                for (tt=ti[k].s;tt<=ti[k].t;tt++)                {                    f[i][j][k]=max(f[i][j][k-1],f[i][j][k]);       //直接继承                    int t=tt-ti[k].s+1;                    int xx=i+t*zz[fan(ti[k].z)][0];                    int yy=j+t*zz[fan(ti[k].z)][1];                    if (mp[xx][yy]&&xx>0&&yy>0&&xx<=n&&yy<=m)                         f[i][j][k]=max(f[i][j][k],f[xx][yy][k-1]+t);                    else break;                }}int main(){    scanf("%d%d%d%d%d",&n,&m,&x,&y,&T);    char s[201];    for (int i=1;i<=n;i++)    {        scanf("%s",&s);        for (int j=0;j<m;j++)            if (s[j]=='x') mp[i][j+1]=0;            else mp[i][j+1]=1;    }    for (int i=1;i<=T;i++)        scanf("%d%d%d",&ti[i].s,&ti[i].t,&ti[i].z),ti[i].z--;    doit();    int ans=0;    for (int i=1;i<=n;i++)        for (int j=1;j<=m;j++)            ans=max(ans,f[i][j][T]);    printf("%d\n",ans);    return 0;}

言归正传,显然我们需要优化
由于钢琴的移动方向是在一段时间内是相同的,因此可以考虑成段dp
令f[t][i][j]表示第t段时间时,钢琴位于(i,j)处时,从第1段时间到第t段时间的最长滑行路程

f[t][i][j]=max{f[t−1][i′][j′]+dist{(i′,j′),(i,j)}}

我们考虑用单调队列优化(然而第一人是怎么想用单调队列的就不得而知了)

队尾出队条件

设队尾为(x,y),新插入的点为(x’,y’)
dis((x,y),(x′,y′)) <= f[x′][y′]-f[x][y] 时,(x,y)可被删掉
直到dis((x,y),(x′,y′)) > f[x′][y′]-f[x][y],我们再把f[x][y]塞到队列里

队首出队条件

队首是转移点,很简单:只要转移点和当前点的距离大于作用时间,我们就出队

在向上和向下移动的时候,转移发生在一列中,所以我们先枚举列
在向左和向右移动的时候,转移发生在一行中,所以我们先枚举行
每次(也就是每一行,每一列以及出现障碍)转移的时候,我们都需要一个新的队列

而且f[i][j][k]只与上一段时间有关,我们可以用两个二维数组(而不是200*200*200的大数组)来实现

//这里写代码片#include<cstdio>#include<cstring>#include<iostream>#include<algorithm>using namespace std;int zz[4][2]={{-1,0},{1,0},{0,-1},{0,1}};int f[201][201],g[201][201];int n,m,x,y,T,tou,wei;bool mp[201][201];struct node{    int x,y;};node Q[201];int dis(node a,node b) {return abs(a.x-b.x)+abs(a.y-b.y);}void insert(node a){    while (tou<wei&&dis(Q[wei],a)<=f[a.x][a.y]-f[Q[wei].x][Q[wei].y]) wei--;    Q[++wei]=a;}int get(node a,int l){    while (tou<wei&&dis(a,Q[tou+1])>l) tou++;         //距离超过作用时间     if (tou==wei) return 0xefefefef;    return f[Q[tou+1].x][Q[tou+1].y]+dis(Q[tou+1],a);}void U(int l){    for (int j=1;j<=m;j++)    {        tou=wei=0;        for (int i=n;i>=1;i--)            if (mp[i][j])            {                node t;                 t.x=i;t.y=j;                g[i][j]=max(f[i][j],get(t,l));                insert(t);            }            else tou=wei=0;       //有障碍了    }    memcpy(f,g,sizeof(f));}void D(int l){    for (int j=1;j<=m;j++)    {        tou=wei=0;        for (int i=1;i<=n;i++)            if (mp[i][j])            {                node t;                t.x=i;t.y=j;                g[i][j]=max(f[i][j],get(t,l));                insert(t);            }            else tou=wei=0;    }    memcpy(f,g,sizeof(f));}void L(int l){    for (int i=1;i<=n;i++)    {        tou=wei=0;        for (int j=m;j>=1;j--)            if (mp[i][j])            {                node t;                t.x=i;t.y=j;                g[i][j]=max(f[i][j],get(t,l));                insert(t);            }            else tou=wei=0;    }    memcpy(f,g,sizeof(f));}void R(int l){    for (int i=1;i<=n;i++)    {        tou=wei=0;        for (int j=1;j<=m;j++)            if (mp[i][j])            {                node t;                t.x=i;t.y=j;                g[i][j]=max(f[i][j],get(t,l));                insert(t);            }            else tou=wei=0;    }    memcpy(f,g,sizeof(f));}int main(){    scanf("%d%d%d%d%d",&n,&m,&x,&y,&T);    char s[201];    for (int i=1;i<=n;i++)    {        scanf("%s",&s);        for (int j=0;j<m;j++)            if (s[j]=='x') mp[i][j+1]=0;            else mp[i][j+1]=1;    }    memset(f,128,sizeof(f));    memset(g,128,sizeof(g));    f[x][y]=0;    for (int i=1;i<=T;i++)    {        int st,ed,z;        scanf("%d%d%d",&st,&ed,&z);        switch(z)        {            case 1:U(ed-st+1);break;            case 2:D(ed-st+1);break;            case 3:L(ed-st+1);break;            case 4:R(ed-st+1);break;        }    }    int ans=0;    for (int i=1;i<=n;i++)        for (int j=1;j<=m;j++)            ans=max(ans,f[i][j]);    printf("%d\n",ans);    return 0;}