P1979 [NOIP]华容道

luogu 传送门
搞了半天,终于搞了出来。
以前写过一个暴力70分的做法(点这里)

在70分的基础上:
注意到只有空格在指定棋子旁边才有意义,所以可以O(n^2)记录状态,记录空格在指定棋子的哪个方向。
因为询问数比较多,所以可以预处理出来【指定棋子不动,空格移动到另一边】和【棋子与空格交换位置】两种情况,以移动步数为边权,以状态为点建图。
代码中有解释

#include
#include
#include
#include
#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  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;
}

你可能感兴趣的:(优化,bfs,最短路,分析)