P1979 华容道

传送门

直接爆力BFS有80分...

讲一下BFS吧

可以发现在一个局面下我们要知道的只有空格的位置和指定棋子的位置

因为其他的不是不可移动就是普通棋子

然后以空格的位置BFS,一个棋子走到空格其实就相当于空格走到棋子并把棋子挤到原本空格的位置

我们就BFS让空格瞎跑看看跑几步可以把指定棋子带到指定位置

但是这样复杂度 $(nm)^2$ 对于500个询问来说太多了

让我们想一想,怎样空格才能带着棋子跑

空格跟棋子交换位置,然后经过一些步数再次走到棋子的另一侧再次交换

所以对于棋子有用的状态只有空格在棋子旁边时

所以我们可以把一个状态看成一个三元组 ( x , y , t ) 表示当前棋子在(x,y)位置,空格在棋子的上右下左(t=0,1,2,3)边

为了方便我们要把三元组化成一维的状态 x*120+y*4+t 这样每个一维的状态唯一对应一个三元组

初始我们先预处理出所有三元组之间的转移,首先是 x,y 不变,t 变化,这个可以BFS预处理出来最少步数

(注意过程中空格不能经过位置(x,y),不然x,y会改变)

然后考虑空格与棋子交换位置

设 空格位置为 xb,yb,棋子位置为 xa,ya 那么初始状态为 (xa,ya,t) ,交换位置后就是 (xb,yb,(t+2)%4)

(空格位置上变下,下变上,左变右,右变左)

实现?枚举所有x,y考虑转移:

for(int i=1;i<=n;i++)
    for(int j=1;j<=m;j++)
    {
        if(!mp[i][j]) continue;//mp是地图状态,0表示不可走
        xa=i; ya=j;
        for(int k=0;k<4;k++)
        {
            xb=xa+xx[k]; yb=ya+yy[k];
            if(mp[xb][yb]) BFS(k);
        }
    }

具体的BFS:

struct data{
    int x,y;
};
queue <data> qaq;
void BFS(int t)
{
    memset(d,-1,sizeof(d)); mp[xa][ya]=0;//空格不能经过x,y
    qaq.push((data){xb,yb}); d[xb][yb]=0;//初始状态
    data u; int tx,ty;
    while(!qaq.empty())
    {
        u=qaq.front(); qaq.pop();
        for(int k=0;k<4;k++)
        {
            tx=u.x+xx[k],ty=u.y+yy[k];
            if(d[tx][ty]!=-1||!mp[tx][ty])/*如果走过或者走不了*/ continue;
            d[tx][ty]=d[u.x][u.y]+1; qaq.push((data){tx,ty});
        }
    }
    mp[xa][ya]=1;//恢复
    for(int k=0;k<4;k++)
    {
        tx=xa+xx[k],ty=ya+yy[k];//求其他的t
        if(d[tx][ty]!=-1) add(xa*120+ya*4+t,xa*120+ya*4+k,d[tx][ty]);//如果可走就连一条边过去
    }
    add(xa*120+ya*4+t,xb*120+yb*4+(t+2)%4,1);//考虑空格与棋子交换位置
}

既然状态变成了点,转移变成了边,直接跑最短路就好了

注意要先把空格移动到指定棋子旁边才能跑最短路,至于空格到棋子旁的最少步数一样用BFS求出

最短路用的是SPFA

注意开始状态和结束状态都有4种,注意特判棋子一开始就在目标位置的情况

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<queue>
using namespace std;
typedef long long ll;
inline int read()
{
    int x=0,f=1; char ch=getchar();
    while(ch<'0'||ch>'9') { if(ch=='-') f=-1; ch=getchar(); }
    while(ch>='0'&&ch<='9') { x=(x<<1)+(x<<3)+(ch^48); ch=getchar(); }
    return x*f;
}
const int N=37,M=1e4+7,xx[4]={0,1,0,-1},yy[4]={1,0,-1,0};
int n,m,Q,ans;
int fir[M],from[M<<2],to[M<<2],val[M<<2],cntt;
inline void add(int a,int b,int c)
{
    from[++cntt]=fir[a];
    fir[a]=cntt; to[cntt]=b; val[cntt]=c;
}
int d[N][N],mp[N][N];
int xa,ya,xb,yb,ex,ey;
struct data{
    int x,y;
};
queue <data> qaq;
void BFS(int t)
{
    memset(d,-1,sizeof(d)); mp[xa][ya]=0;//空格不能经过x,y
    qaq.push((data){xb,yb}); d[xb][yb]=0;//初始状态
    data u; int tx,ty;
    while(!qaq.empty())
    {
        u=qaq.front(); qaq.pop();
        for(int k=0;k<4;k++)
        {
            tx=u.x+xx[k],ty=u.y+yy[k];
            if(d[tx][ty]!=-1||!mp[tx][ty])/*如果走过或者走不了*/ continue;
            d[tx][ty]=d[u.x][u.y]+1; qaq.push((data){tx,ty});
        }
    }
    mp[xa][ya]=1;//恢复
    if(t==4) return;//t==4表示此时是求空格到棋子旁的最少步数
    for(int k=0;k<4;k++)
    {
        tx=xa+xx[k],ty=ya+yy[k];//求其他的t
        if(d[tx][ty]!=-1) add(xa*120+ya*4+t,xa*120+ya*4+k,d[tx][ty]);//如果可走就连一条边过去
    }
    add(xa*120+ya*4+t,xb*120+yb*4+(t+2)%4,1);//考虑空格与棋子交换位置
}
int dis[M];
bool vis[M];
queue <int> q;
void spfa()
{
    memset(dis,127,sizeof(dis)); memset(vis,0,sizeof(vis));
    int tx,ty,id,x;
    for(int k=0;k<4;k++)//初始空格可以有4种状态,上右下左
    {
        tx=xa+xx[k],ty=ya+yy[k]; id=xa*120+ya*4+k;
        if(d[tx][ty]!=-1) dis[id]=d[tx][ty],q.push(id),vis[id]=1;
    }
    while(!q.empty())//跑spfa
    {
        x=q.front(); q.pop(); vis[x]=0;
        for(int i=fir[x];i;i=from[i])
        {
            int &v=to[i];
            if(dis[v]>dis[x]+val[i])
            {
                dis[v]=dis[x]+val[i];
                if(!vis[v]) q.push(v),vis[v]=1;
            }
        }
    }
}
int main()
{
    //freopen("testdata.in","r",stdin);
    //freopen("testdata.out","w",stdout);
    n=read(); m=read(); Q=read();
    for(int i=1;i<=n;i++) for(int j=1;j<=m;j++) mp[i][j]=read();
    for(int i=1;i<=n;i++)
        for(int j=1;j<=m;j++)
        {
            if(!mp[i][j]) continue;
            xa=i; ya=j;
            for(int k=0;k<4;k++)
            {
                xb=xa+xx[k]; yb=ya+yy[k];
                if(mp[xb][yb]) BFS(k);
            }
        }
    while(Q--)
    {
        xb=read(); yb=read(); xa=read(); ya=read(); ex=read(); ey=read();
        if(xa==ex&&ya==ey) { printf("0
"); continue; }//注意特判
        BFS(4);/*求出空格到棋子的最少步数*/ spfa(); ans=2e9+7;
        for(int k=0;k<4;k++) ans=min(ans,dis[ex*120+ey*4+k]);//有四种结束状态
        printf("%d
",ans==2e9+7 ? -1 : ans);
    }
    return 0;
}

 最后附上80分的BFS:

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<queue>
using namespace std;
typedef long long ll;
inline int read()
{
    int x=0,f=1; char ch=getchar();
    while(ch<'0'||ch>'9') { if(ch=='-') f=-1; ch=getchar(); }
    while(ch>='0'&&ch<='9') { x=(x<<1)+(x<<3)+(ch^48); ch=getchar(); }
    return x*f;
}
const int N=37,xx[4]={0,1,0,-1},yy[4]={1,0,-1,0};
int n,m,Q,ans;
int mp[N][N];
struct data
{
    int x,y,px,py,stp;
};
int vis[N][N][N][N];//vis存的是时间戳,就不用memset了
int xa,ya,xb,yb,ex,ey,tim;
int BFS()
{
    if(xb==ex&&yb==ey) return 0;
    queue <data> q;
    q.push((data){xa,ya,xb,yb,0}); vis[xa][ya][xb][yb]=tim;//初始状态
    data u; int tx,ty,px,py;
    while(!q.empty())
    {
        u=q.front(); q.pop();
        for(int k=0;k<4;k++)//空格到处瞎跑
        {
            tx=u.x+xx[k],ty=u.y+yy[k]; if(tx<0||tx>n||ty<0||ty>m||!mp[tx][ty]) continue;
            px=u.px,py=u.py; if(tx==px&&ty==py)/*如果空格刚好与指定棋子交换*/ px=u.x,py=u.y;
            if(vis[tx][ty][px][py]==tim) continue; vis[tx][ty][px][py]=tim;
            if(px==ex&&py==ey) return u.stp+1;//到达终点状态
            q.push((data){tx,ty,px,py,u.stp+1});
        }
    }
    return -1;
}
int main()
{
    n=read(); m=read(); Q=read();
    for(int i=1;i<=n;i++) for(int j=1;j<=m;j++) mp[i][j]=read();
    for(tim=1;tim<=Q;tim++)
    {
        xa=read(); ya=read(); xb=read(); yb=read(); ex=read(); ey=read();
        printf("%d
",BFS());
    }
    return 0;
}
暴力
原文地址:https://www.cnblogs.com/LLTYYC/p/9869942.html