day2 华容道

这个第三题,若在考场上碰到,多半是一脸懵逼。。。满分确实很困难,但是利用纯正的广搜其实是可以过六十到七十分的(取决于细节),下面给出代码:

#include<cstdio>
#include<queue>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
const int fx[2][5]={{0,0,1,-1},{-1,1,0,0}};
int f[35][35],q,n,m;
bool vis[35][35][35][35];
struct data{
    int bx,by,lx,ly,step;
}node[810005];
bool pd(int a,int b,int c,int d)
{
    if(!f[c][d]||c<1||d<1||c>n||d>m)
    {
        return false;
    }
    if(vis[a][b][c][d])return false;
    vis[a][b][c][d]=true;
    return true;
}
void bfs()
{
    queue<int>q;
    int ex,ey,cnt=0;
    memset(vis,0,sizeof(vis));
    scanf("%d%d%d%d%d%d",&node[1].bx,&node[1].by,&node[1].lx,&node[1].ly,&ex,&ey);
    if(node[1].lx==ex&&node[1].ly==ey){
    cout<<0<<endl;return ;}
    vis[node[1].lx][node[1].ly][node[1].bx][node[1].by]=true;
    q.push(++cnt);
    node[cnt].step=0;
    while(!q.empty())
    {
        int no=q.front();q.pop();
        for(int i=0;i<=3;i++)
        {
            int ox=node[no].lx,oy=node[no].ly;
            int px=node[no].bx+fx[0][i],py=node[no].by+fx[1][i];
            if(ox==px&&oy==py)ox=node[no].bx,oy=node[no].by;
            if(pd(ox,oy,px,py))
            {
                q.push(++cnt);
                node[cnt].bx=px,node[cnt].by=py;
                node[cnt].lx=ox,node[cnt].ly=oy;
                node[cnt].step=node[no].step+1;
                if(ox==ex&&oy==ey){
                    cout<<node[cnt].step<<endl;return ;}
            }
        }
    }
    cout<<-1<<endl;
    return ;
}
int main()
{
    freopen("puzzle.in","r",stdin);
    freopen("puzzle.out","w",stdout);
    cin>>n>>m>>q;
    for (int i=1;i<=n;i++)
     for (int j=1;j<=m;j++)
     scanf("%d",&f[i][j]);
    for (int i=1;i<=q;i++)
    {
        bfs();
    }
    return 0;
}

这个题的正解,甚是困难(也可能是题量不够吧。。。⊙﹏⊙b汗),先说说具体思路吧。之前的bfs中,有太多的冗余运算,导致后面的超时。那么

首先,如果要移动目标棋子,那么我们首先必须要将空格移到该棋子的上下左右四个方向上相邻位置之一,然后才可以移动该棋子。

然后,我们分析该棋子移动时候的性质:

棋子每次可以移动,仅当空格位于其相邻位置的时候,每次移动完棋子,空格总会在棋子相邻的位置,那么我们发现,对于棋子在某一位置,然后空格又在其四个方向上某一相邻位置时,棋子要想某一方向移动一个时的花费的步数是一定的,那么,就可以先进行一次预处理,预处理出对于目标棋子在上述条件下每次移动所需的步数。

然后,预处理完成之后,我们会发现每次查询都会变成一个求最短路的问题,用Dijstra或SPFA的话,可以在时限范围内解决。下面给出代码(摘自网络):

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <queue>
#define MaxN 35
using namespace std;
const int 
    INF=~0U>>2,
    dx[]={0,0,-1,1},
    dy[]={-1,1,0,0};//注意顺序,才易实现0变1,1变0;2变3,3变2 
int mat[MaxN][MaxN],dis[MaxN][MaxN][4];
bool vis[MaxN][MaxN][4];
int step[MaxN][MaxN][4][4];
int d[MaxN][MaxN];
int n,m,q,test,ex,ey,sx,sy,tx,ty;
struct node
{
       int x,y;
};
struct node2
{
       int x,y,k;
};
bool inside(int x, int y)
{
    return (x>=1&&x<=n&&y>=1&&y<=m);
}
int spfa()
{
    queue<node2> q;
    memset(vis,false,sizeof(vis));
    while(!q.empty()) q.pop();//局部队列,可能非空,所以清空 
    for(int k=0;k<4;k++)//把初始位置棋子与空格相邻,四个方向组成的四种可能的步数入队,当成四个源点。 
        if(dis[sx][sy][k]!=INF)
        {    
            q.push((node2){sx,sy,k});
            vis[sx][sy][k]=true;
        }
    while(!q.empty())
    {    
        int x=q.front().x;
        int y=q.front().y;
        int k=q.front().k;
        q.pop();
        vis[x][y][k]=false;
        for(int i=0;i<4;i++)
        {
            int _x=x+dx[i];//棋子(x,y)扩展的点(_x,_y)
            int _y=y+dy[i];
            if(inside(_x,_y))
            if(mat[_x][_y])
            if(step[x][y][k][i]!=INF)//棋子(x,y)k方向空格可以移到棋子(x,y)i方向。 
            if(dis[_x][_y][i^1]>dis[x][y][k]+step[x][y][k][i]+1) 
            {//棋子(x,y)k方向空格移到棋子(x,y)i方向,再把棋子移到空格上,所以+1,空格变成i^1方向。
                dis[_x][_y][i^1]=dis[x][y][k]+step[x][y][k][i]+1;
                if (not vis[_x][_y][i^1])
                 { 
                   q.push((node2){_x,_y,i ^ 1 });
                   vis[_x][_y][i^1]=true;
                 }
            }
        }
    }
    int ans=INF;
    for(int i=0;i<4;i++)
        if(dis[tx][ty][i]<ans)
            ans=dis[tx][ty][i];//找出目标位置与空格相邻四种情况最少步数 
    return (ans==INF)? -1:ans;
}
int bfs(int sx,int sy,int tx,int ty)//将空格从(sx,sy)移到(tx,ty)的步数 
{
    if(!mat[sx][sy])
        return INF;
    if(!mat[tx][ty])
        return INF;
    for(int i=1;i<=n;i++)
        for(int j=1;j<=m;j++) 
            d[i][j]=INF;//INF可以做为没有到过的标志 
    d[sx][sy]=0;
    queue<node> q;//局部队列,可能非空,所以清空 
    while(!q.empty()) q.pop();
    q.push((node){sx,sy});
    while(!q.empty())
    {
        if(d[tx][ty]!=INF)
          return d[tx][ty];
        int x=q.front().x;
        int y=q.front().y;
        q.pop();
        for(int i=0;i<4;i++)
        {
            int _x=x+dx[i];
            int _y=y+dy[i];
            if(inside(_x,_y))
                if(mat[_x][_y]&&d[_x][_y]==INF)
                {
                    d[_x][_y]=d[x][y]+1;
                    //if(d[tx][ty]!=INF)
                    //   return d[tx][ty];
                    //与下面不等价,有可能(sx,sy)与(tx,ty)一样,所以最好放在出队前判断 
                    //if (_x==tx&&_y==ty) return d[tx][ty];????
                    q.push((node){_x,_y});
                }
        }
    }
    return INF;
}
void init()
{
    for(int i=1;i<=n;i++)
        for(int j=1;j<=m;j++)
        {
             int v=mat[i][j];
            mat[i][j]=0;//此位置可能是0或1,均改成0,因为移动空格不能移动棋子(i,j) 
            for (int k=0;k<4;k++)
                for (int l=0;l<4;l++) 
                     step[i][j][k][l]=bfs(i+dx[k],j+dy[k],i+dx[l],j+dy[l]);
                //step[i][j][k][l] 棋子(i,j)k方向相邻的空格移动到相邻的l方向的步数,
             mat[i][j]=v;//还原
        }
}    
int getAns()
{
    scanf("%d%d%d%d%d%d",&ex,&ey,&sx,&sy,&tx,&ty);
    if(sx==tx&&sy==ty)//初始位置与目标位置同,0步
        return 0;
    if(sx==ex&&sy==ey)//初始位置与空格位置同,无解
        return -1;
    if(!inside(ex,ey)||!inside(sx,sy)||!inside(tx,ty))//三个位置至少有一个在棋盘外
        return -1;
    if(!mat[ex][ey]||!mat[sx][sy]||!mat[tx][ty])////三个位置至少有一个是固定的
        return -1;
    for(int i=1;i<=n;i++)
        for (int j=1;j<=m;j++)
            for (int k=0;k<4;k++)
                dis[i][j][k]=INF;
    mat[sx][sy]=0;//此时一定是1,改成0,因为前面已经判断过 
    for(int k=0;k<4;k++)
         dis[sx][sy][k]=bfs(ex,ey,sx+dx[k],sy+dy[k]);
    //dis[sx][sy][k]表示将空格移到(sx,sy)相邻并在k方向上的步骤 
    mat[sx][sy]=1; //还原 
    return spfa();
}
int main()
{
    freopen("puzzle.in","r",stdin);
    freopen("puzzle.out","w",stdout);
    scanf("%d%d%d",&n,&m,&test);
    for(int i=1;i<=n;i++)
        for(int j=1;j<=m;j++)
            scanf("%d",&mat[i][j]);
    init();
    while(test--)
        printf("%d\n",getAns());
    return 0;
}

代码很长,慢慢理解。。。

清清正正射命丸文是也~

原文地址:https://www.cnblogs.com/Ayateriteri/p/5664790.html