[Luogu P6474][NOI Online #2 入门组]荆轲刺秦王 题解(BFS)

官方视频题解

My Blog

\(Dis[i][j][u1][u2]\) 表示起点到达点 \((i,j)\),使用了 \(u1\) 次隐身,\(u2\) 次瞬移的最短时间,\(Sx,Sy,Tx,Ty\) 分别表示起点和终点的坐标。

那么起点就是 \((Sx,Sy,0,0)\),然后使用 BFS 求最短时间,每一次枚举下一个坐标,是否隐身及瞬移,最后在所有能够到达的 \((Tx,Ty,?,?)\)中取最优即为答案。

预处理

在 BFS 的过程中我们需要知道一个点是否被卫兵所观察,如果每读入一个卫兵就暴力修改的话时间复杂度最坏可以达到 \(O(nma^2)\),显然会超时,那么需要进行优化。

方案 1:(官方题解)观察到一个卫兵所能观察到的点一定是一个菱形,那么枚举菱形的每一行就是覆盖连续的一段,这个可以用差分+前缀和解决,时间复杂度 \(O(nma)\)

方案 2:(考场做法)把所有卫兵放入优先队列,每次取出 \(a\) 最大的卫兵向四周覆盖(插入一个 \(a-1\) 的卫兵,若之前这个点已经被 \(a\) 更大的士兵覆盖则跳过),就是一个类似最短路的过程,时间复杂度 \(O(nm\log(nm))\)

方案 3:对方案 2 进行优化,因为 \(a\) 最大只有 \(350\),那么可以开 \(O(a)\) 个桶来维护卫兵,从高往低扫,时间复杂度 \(O(nm)\)

BFS

如果直接做的话复杂度就是 \(O(n\times m\times c1\times c2\times 24)\approx 7.5\times 10^8\),其中 \(24\) 表示 BFS 中每个状态转移的复杂度,显然跑 3s 还是有点吃力(本机 8s,或许CCF少爷机能过?

优化 1:最优性等剪枝(但我没试过不知道有没有用 没加这个T成 \(95\)

优化 2:直接检查是否需要隐身,不需要枚举,因为不隐身显然更优,这样可以减去一个 \(2\) 的常数且减去一些无用状态(虽然这很显然 但是当时我没想到

优化 3:卡常 我当时没想到上述优化就选择了大力卡常,将 BFS 中的每一个状态 \((x,y,u1,u2)\) 通过位运算压缩到一个 int 中,并且将 \(Dis\) 数组压成了一维,然后就是喜闻乐见的 register 及手写队列之类的(本机破i5 5s)

复杂度 \(O(n\times m\times c1\times c2\times 12)\approx 3.7\times 10^8\) 并且跑不满,大概能过

代码:

#include <queue>
#include <cstdio>
#include <cstring>
#define cint const int
#define rint register int

const int Inf=0x3f3f3f3f,Dx[]={-1,-1,-1,0,1,1,1,0},Dy[]={-1,0,1,1,1,0,-1,-1};
int n,m,c1,c2,D,Sx,Sy,Tx,Ty,Md=Inf;
int s[355][355],ss[355][355],Dis[50000005],q[35000005],qh,qt;
//s[i][j]表示是否被卫兵观察,ss[i][j]表示是否有卫兵,Dis[i]表示最短时间,q[i]为BFS队列
char r[15];

struct Cr{int x,y,a;inline bool operator<(const Cr& o)const{return a<o.a;}};
#define H(a,b,c,d) (((a)<<17)|((b)<<8)|((c)<<4)|(d))
//状态压缩函数

int Get()//读入一个点
{
    scanf("%s",r);
    if(*r=='.')return 0;
    if(*r=='S')return -1;
    if(*r=='T')return -2;
    int a=0,l=strlen(r);
    for(int i=0;i<l;++i)a=a*10+(r[i]^48);
    return a;
}

void PCover()
//预处理,用的是优先队列的版本
{
    std::priority_queue<Cr> q;
    for(int i=1;i<=n;++i)
        for(int j=1;j<=m;++j)
            if(s[i][j]>0)
                q.push((Cr){i,j,s[i][j]});
    for(int x,y,a;!q.empty();)
    {
        x=q.top().x,y=q.top().y,a=q.top().a,q.pop();
        if(s[x][y]>a)continue;
        for(int i=1,nx,ny;i<8;i+=2)
        {
            nx=x+Dx[i],ny=y+Dy[i];
            if(nx<1||nx>n||ny<1||ny>m||s[nx][ny]>=a-1)continue;
            q.push((Cr){nx,ny,s[nx][ny]=a-1});
        }
    }
}

void BFS()
{
    memset(Dis,0x3f,sizeof Dis),Dis[q[0]=H(Sx,Sy,0,0)]=0;
    for(rint x,y,u1,u2,d,Nv,Wv;qh<=qt;)
    {
        Nv=q[qh++],x=Nv>>17,y=Nv>>8&0x1ff,u1=Nv>>4&0xf,u2=Nv&0xf,d=Dis[Nv];
        //取出队首并且解压缩
        if(d>=Md||ss[x][y]>0)continue;
        if(x==Tx&&y==Ty)Md=d;//最优性剪枝
        for(rint i=0,nx,ny,j;i<8;++i)
        {
            nx=x+Dx[i],ny=y+Dy[i];
            if(nx<1||nx>n||ny<1||ny>m)continue;
            j=s[nx][ny]>0;//是否需要隐身
            if(u1+j<=c1&&Dis[Wv=H(nx,ny,u1+j,u2)]>d+1)Dis[q[++qt]=Wv]=d+1;
            if(i&1)//瞬移技能
            {
                nx=x+Dx[i]*D,ny=y+Dy[i]*D;
                if(nx<1||nx>n||ny<1||ny>m)continue;
                j=s[nx][ny]>0;
                if(u1+j<=c1&&u2+1<=c2&&Dis[Wv=H(nx,ny,u1+j,u2+1)]>d+1)Dis[q[++qt]=Wv]=d+1;
            }
        }
    }
}

int main()
{
    //freopen("in.txt","r",stdin);
    //freopen("bandit.out","w",stdout);
    scanf("%d%d%d%d%d",&n,&m,&c1,&c2,&D);
    for(int i=1;i<=n;++i)
        for(int j=1;j<=m;++j)
            if((ss[i][j]=s[i][j]=Get())==-1)Sx=i,Sy=j;
            else if(s[i][j]==-2)Tx=i,Ty=j;
    PCover(),BFS();
    rint A1=Inf,A2=Inf,A3=Inf,d;
    for(rint i=0;i<=c1;++i)
        for(rint j=0;j<=c2;++j)
            if((d=Dis[H(Tx,Ty,i,j)])<A1||(d==A1&&i+j<A2+A3)||(d==A1&&i+j==A2+A3&&i<A2))
                A1=d,A2=i,A3=j;
    if(A1==Inf)puts("-1");
    else printf("%d %d %d\n",A1,A2,A3);
    return 0;
}
posted @ 2020-04-28 08:35  LanrTabe  阅读(612)  评论(0编辑  收藏  举报