zoukankan      html  css  js  c++  java
  • [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(nmlog(nm)))

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

    BFS

    如果直接做的话复杂度就是 (O(n imes m imes c1 imes c2 imes 24)approx 7.5 imes 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 imes m imes c1 imes c2 imes 12)approx 3.7 imes 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
    ",A1,A2,A3);
        return 0;
    }
    
  • 相关阅读:
    ST表——————一失足成千古恨系列2
    五一清北学堂培训之图论
    最长上升子序列的教训———一失足成千古恨系列1
    五一清北学堂培训之Day 3之DP
    [bzoj1057][ZJOI2007]棋盘制作
    [bzoj1010][HNOI2008]玩具装箱TOY
    [bzoj2326][HNOI2011]数学作业
    如何使用矩阵乘法加速动态规划——以[SDOI2009]HH去散步为例
    [bzoj1060][zjoi2007]时态同步
    [bzoj1046][HAOI2007]上升序列
  • 原文地址:https://www.cnblogs.com/LanrTabe/p/12781938.html
Copyright © 2011-2022 走看看