洛谷 P1979 华容道[noip2013] (bfs+spfa)

题目描述

【问题描述】

小 B 最近迷上了华容道,可是他总是要花很长的时间才能完成一次。于是,他想到用编程来完成华容道:给定一种局面, 华容道是否根本就无法完成,如果能完成, 最少需要多少时间。

小 B 玩的华容道与经典的华容道游戏略有不同,游戏规则是这样的


在一个 n*m 棋盘上有 n*m 个格子,其中有且只有一个格子是空白的,其余 n*m-1个格子上每个格子上有一个棋子,每个棋子的大小都是 1*1 的;

有些棋子是固定的,有些棋子则是可以移动的;

任何与空白的格子相邻(有公共的边)的格子上的棋子都可以移动到空白格子上。


游戏的目的是把某个指定位置可以活动的棋子移动到目标位置。

给定一个棋盘,游戏可以玩 q 次,当然,每次棋盘上固定的格子是不会变的, 但是棋盘上空白的格子的初始位置、 指定的可移动的棋子的初始位置和目标位置却可能不同。第 i 次

玩的时候, 空白的格子在第 EXi 行第 EYi 列,指定的可移动棋子的初始位置为第 SXi 行第 SYi列,目标位置为第 TXi 行第 TYi 列。

假设小 B 每秒钟能进行一次移动棋子的操作,而其他操作的时间都可以忽略不计。请你告诉小 B 每一次游戏所需要的最少时间,或者告诉他不可能完成游戏。

输入输出格式

输入格式:

第一行有 3 个整数,每两个整数之间用一个空格隔开,依次表示 n、m 和 q;

接下来的 n 行描述一个 n*m 的棋盘,每行有 m 个整数,每两个整数之间用一个空格隔开,每个整数描述棋盘上一个格子的状态,0 表示该格子上的棋子是固定的,1 表示该格子上的棋子可以移动或者该格子是空白的。接下来的 q 行,每行包含 6 个整数依次是 EXi、EYi、SXi、SYi、TXi、TYi,每两个整数之间用一个空格隔开,表示每次游戏空白格子的位置,指定棋子的初始位置和目标位置。

输出格式:

输出有 q 行,每行包含 1 个整数,表示每次游戏所需要的最少时间,如果某次游戏无法完成目标则输出−1。

注意

这道题记录状态不光只记录没一个点的坐标 [x][y],还要记录记录 来向,也就是通过哪种移动走到点[x][y]的,其实记成前趋坐标也没问题,属于压缩状态了。
有时候不记录这个点的具体坐标,反而记录的是前趋坐标,以及到达方式。要留心看好。
每一次搜的if条件不一样

整体思路

1、 在把图输入之后,就预处理每一个点到各个点的步数。
是bfs没毛病,准确来说是 每一个点通过何种方式到各个点的步数
而且全程不允许经过初始点(每一次bfs的起点),也就是说只能搜到不需跨过初始点就可以到达的点

于是就可以得到如下算法:
初始点不入队,而是四个相邻点入队(并记录来向dir) 其实很好记的啦,如果入门的时候做过八皇后之类题就是什么操作的
再开一个次级队列,进行一本正经的dfs(不记录来向),得到一组dis,搜完了存进 含来向和去向的最终路权 路权是我们算出来的
其实这一次大搜索只为处理出初始点的四个相邻点的三个相邻点(第四个方向是初始点,没必要)
觉得很费力气?直接赋值就好了?我也是这样想的,原作者说是为了防止直接到不了,我觉得好似不存在,总之保险hh

2、 正式输入某一特定情况后,我们急于利用预处理的路权跑spfa,可惜的是,我们的空格和出发点s不是一个点的。
于是我们需要把空格 拉到 s 附近,既然是附近,那就是保持s的初始状态,所以搜索时不可以动s点。
(说实话,这道题每一次搜索的处理前条件不完全相同,要想明白目的)

于是就可以得到如下算法:
不带方向地进行预搜索,bfs即可,第一次碰到,即最短

3、当空格在s附近我们就可以开始移动了

于是就不得不得到如下算法:
带方向的给s点四个方向的点赋路权(预搜索得到),并带方向地把s入队 (和预处理不同,不存四周点,存原点与原点去向)
进行SPFA从现在开始s已经完成存在的使命,空格代替s开始自由飞翔,回顾一下就是,利用s与e之间的距离给s周围的四个点赋最初路权,即“把空格 拉到 s 附近”,然后利用预处理路权进行最短路算法
每一个队首的点u,需要首先利用”直接交换的性质松弛 u 指向 的那个点 (这里有个漂亮的换向操作)
这个时候我就可以解释这个的作用了,就是对于任意点x,他的指向就是空格在的地方,来表示这个点的可移动趋势。
然后再进行其余三向点的松弛,这个时候可以跨过s了,但是这个时候我们松弛的不是s的四周点,是在另外三周方向下的s点
这个时候就可以解释我刚才蒙混过关没说的一个点:为什么赋路权要全图bfs,因为对于任意的点x,让空格从方向1到方向2可能需要跨过障碍物,就比如右上角,左下角,左上角,右下角的位置可能不可以移动。
然后判一下四个来向下的终点路径 其实叫它来向挺对的,因为,任意一个点x,他的空格在哪个方向,他就是从哪个方向来的,如果不是初始值的话。

代码

#include<cstdio>
#include<iostream>
#include<queue>
#include<cstring>
using namespace std;
#define mem(a,b) memset(a,b,sizeof(a))
int dx[5]={0 ,-1,0,1};
int dy[5]={-1, 0,1,0};
struct dogs{int x,y,dir;}dog[1000];
struct cats{int x,y;}cat[1000];
int map[50][50],dis[50][50],cos[50][50][5][5],dp[50][50][5];
bool vis[50][50][5];
int Sx,Sy,Ex,Ey,Tx,Ty,n,m,q;
void prebfs(int sx,int sy)
{
    queue<dogs> st;
    for(int i=0;i<4;i++)
    {
        int xx=sx+dx[i],yy=sy+dy[i];
        if(xx>0&&xx<=n&&yy>0&&yy<=m&&map[xx][yy])
        {
        st.push((dogs){xx,yy,i});
        //cout<<xx<<" "<<yy<<" "<<i<<endl;
        }
    }
    //cout<<"~~~~~~~~~~~~~"<<endl;
    while(!st.empty())
    {
        dogs b=st.front();st.pop();
        mem(dis,0x3f);
        int bx=b.x,by=b.y,ret=b.dir;
        queue<cats>q;
        q.push((cats){bx,by});
        dis[bx][by]=0;
    //cout<<"("<<bx<<","<<by<<")="<<ret<<endl;
        while(!q.empty())
        {
            cats u=q.front();q.pop();
            for(int i=0;i<4;i++)
            {
                int xx=u.x+dx[i],yy=u.y+dy[i];
                if(xx>0&&xx<=n&&yy>0&&yy<=m&&map[xx][yy]&&(xx!=sx||yy!=sy)&&dis[xx][yy]==0x3f3f3f3f)
                {

                    dis[xx][yy]=dis[u.x][u.y]+1;
                    q.push((cats){xx,yy});
                    //cout<<xx<<" "<<yy<<" "<<dis[xx][yy]<<endl; 
                }
            }
        }
        //cout<<"____________"<<endl; 
        for(int i=0;i<4;i++)
        {
            int xx=sx+dx[i],yy=sy+dy[i];
            if(xx>0&&xx<=n&&yy>0&&yy<=m&&map[xx][yy]&&(xx!=bx||yy!=by)&&dis[xx][yy]!=0x3f3f3f3f)
            {
                cos[sx][sy][ret][i]=dis[xx][yy];
                //cout<<cos[sx][sy][ret][i]<<" ";
            }
            //cout<<endl;
        }
    }
}
void preblock()
{
    queue<cats>q;
    mem(dis,0x3f);
    dis[Ex][Ey]=0;
    q.push((cats){Ex,Ey});
    while(!q.empty())
    {
        cats u=q.front();q.pop();   
        for(int i=0;i<4;i++)
        {
            int xx=u.x+dx[i],yy=u.y+dy[i];
            if(xx>0&&yy>0&&xx<=n&&yy<=m&&map[xx][yy]&&
               (xx!=Sx||yy!=Sy)&&dis[xx][yy]==0x3f3f3f3f)
            {
                dis[xx][yy]=dis[u.x][u.y]+1;
                q.push((cats){xx,yy});
            }
        }   
    }

}
int bfs()
{
    if(Sx==Tx&&Sy==Ty)return 0;
    preblock();
    queue<dogs>q;
    mem(dp,0x3f);mem(vis,0);
    for(int i=0;i<4;i++)
    {
        int xx=Sx+dx[i];
        int yy=Sy+dy[i];
        if(xx>0&&yy>0&&xx<=n&&yy<=m&&map[xx][yy]&&dis[xx][yy]!=0x3f3f3f3f)
        {
                dp[Sx][Sy][i]=dis[xx][yy];//这里为什么不是xx yy? 
                q.push((dogs){Sx,Sy,i});
                vis[Sx][Sy][i]=1;
        }
    }
    while(!q.empty())
    {
        dogs u=q.front();q.pop();
        int ux=u.x,uy=u.y,ret=u.dir;vis[ux][uy][ret]=0;//spfa不可以忘了消去标记 
        int xx=ux+dx[ret],yy=uy+dy[ret];//不找对点,直接用uxuy交换。 
        if(dp[xx][yy][(ret+2)%4]>dp[ux][uy][ret]+1)
        {
            dp[xx][yy][(ret+2)%4]=dp[ux][uy][ret]+1;
            if(!vis[xx][yy][(ret+2)%4])
            {
                q.push((dogs){xx,yy,(ret+2)%4});
                vis[xx][yy][(ret+2)%4]=1;
            }
        }
        for(int i=0;i<4;i++)
        {
            int xx=ux+dx[i],yy=uy+dy[i];
            if(xx>0&&yy>0&&xx<=n&&yy<=m&&map[xx][yy]&&
               i!=u.dir&&cos[ux][uy][u.dir][i]!=0x3f3f3f3f)
            //这里为什么可以经过SxSy了呢?为什么i!=dir?     
/*          if(dp[xx][yy][i]>dp[ux][uy][u.dir]+cos[ux][uy][u.dir][i])
                dp[xx][yy][i]=dp[ux][uy][u.dir]+cos[ux][uy][u.dir][i];
                if(!vis[xx][yy][i])
                    q.push((dogs){xx,yy,i});
                    vis[xx][yy][i]=1;
*/
            if(dp[ux][uy][i]>dp[ux][uy][u.dir]+cos[ux][uy][u.dir][i])
            {
                dp[ux][uy][i]=dp[ux][uy][u.dir]+cos[ux][uy][u.dir][i]; 
                if(!vis[ux][uy][i])
                {
                    q.push((dogs){ux,uy,i});
                    vis[ux][uy][i]=1;
                }
            }       
        }
    }
    int ans=0x3f3f3f3f;
    for(int i=0;i<4;i++)
    {
        ans=min(ans,dp[Tx][Ty][i]);
    }
    if(ans==0x3f3f3f3f)return -1;
    return ans;
}
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",&map[i][j]);
    mem(cos,0x3f);
    for(int i=1;i<=n;i++)
        for(int j=1;j<=m;j++)
        if(map[i][j])prebfs(i,j);
for(int i=1;i<=q;i++)
{
scanf("%d%d%d%d%d%d",&Ex,&Ey,&Sx,&Sy,&Tx,&Ty);
printf("%d\n",bfs());
}
//system("pause");
return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值