P1979 [NOIP]华容道
luogu 传送门
搞了半天,终于搞了出来。
以前写过一个暴力70分的做法(点这里)
在70分的基础上:
注意到只有空格在指定棋子旁边才有意义,所以可以O(n^2)记录状态,记录空格在指定棋子的哪个方向。
因为询问数比较多,所以可以预处理出来【指定棋子不动,空格移动到另一边】和【棋子与空格交换位置】两种情况,以移动步数为边权,以状态为点建图。
代码中有解释
#include<cstdio>
#include<iostream>
#include<cstring>
#include<queue>
#define M 40009
#define INF 0x3f3f3f3f//赋0x7fffffff会爆
using namespace std;
int dx[]={1,-1,0,0},dy[]={0,0,1,-1};
int head[M],nxt[M],to[M],cost[M],cnt,tot,dis[M];
int a[35][35],num[35][35][5];//num[i][j][k]表示指定棋子在i,j这个位置,空白格子在k这个位置这个状态编号
int ex,ey,sx,sy,tx,ty,n,m,Q;
bool vis[35][35],f[M];
struct H{int x,y,step;};
void add(int x,int y,int z)
{
to[++cnt]=y;
nxt[cnt]=head[x];
head[x]=cnt;
cost[cnt]=z;
}
int bfs(int ax,int ay,int bx,int by,int cx,int cy)//不经过cx,cy
{
if(ax==bx&&ay==by) return 0;
memset(vis,0,sizeof(vis));
queue <H> q;
q.push((H){ax,ay,0});
vis[ax][ay]=1;
while(!q.empty())
{
H k=q.front();q.pop();
if(k.x==bx&&k.y==by) return k.step;
for(int i=0;i<4;i++)
{
int x=k.x+dx[i],y=k.y+dy[i];
if(x>=1&&x<=n&&y>=1&&y<=m&&a[x][y]&&(x!=cx||y!=cy)&&!vis[x][y])
{
q.push((H){x,y,k.step+1});
vis[x][y]=1;
if(x==bx&&y==by) return k.step+1;
}
}
}
return INF;
}
int spfa()
{
queue <int> q;
if(sx==tx&&sy==ty) return 0;
for(int i=1;i<=tot;i++) dis[i]=INF;
for(int k=0;k<4;k++)
if(num[sx][sy][k]){
dis[num[sx][sy][k]]=bfs(ex,ey,sx+dx[k],sy+dy[k],sx,sy);//将空白格子移到s格子周围
q.push(num[sx][sy][k]);
//f[num[sx][sy][k]]=1;
}
while(!q.empty())
{
int k=q.front();q.pop();
f[k]=0;
for(int i=head[k];i;i=nxt[i])
{
int t=to[i];
if(dis[t]>dis[k]+cost[i])
{
dis[t]=dis[k]+cost[i];
if(!f[t]) q.push(t),f[t]=1;
}
}
}
int ans=INF;
for(int k=0;k<4;k++)
if (num[tx][ty][k])
ans=min(ans,dis[num[tx][ty][k]]);
return ans==INF?-1:ans;
}
void init()
{
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
for(int k=0;k<4;k++)
if(a[i][j]&&a[i+dx[k]][j+dy[k]])
num[i][j][k]=++tot;
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
for(int k=0;k<4;k++)
if(num[i][j][k])//如果这个状态存在,那么交换位置后的状态也一定存在
add(num[i][j][k],num[i+dx[k]][j+dy[k]][k^1],1);//^1是相反方向,表示与空白格子交换位置边权为1
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
for(int k=0;k<4;k++)
for(int l=0;l<4;l++)
if(l!=k&&num[i][j][k]&&num[i][j][l])//空白格子从某个方向移动到另一方向
add(num[i][j][k],num[i][j][l],bfs(i+dx[k],j+dy[k],i+dx[l],j+dy[l],i,j));
}
int main()
{
scanf("%d%d%d",&n,&m,&Q);
for(int i=1;i<=n;i++) for(int j=1;j<=m;j++) scanf("%d",&a[i][j]);
init();
while(Q--)
{
scanf("%d%d%d%d%d%d",&ex,&ey,&sx,&sy,&tx,&ty);
printf("%d\n",spfa());
}
return 0;
}