[Luogu P6474][NOI Online #2 入门组]荆轲刺秦王 题解(BFS)
设 \(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;
}