【noip】 华容道 搜索

12 篇文章 0 订阅

这几天把13年的提高做了,最后两道题参考了网上许多代码,最后终于改出来了,这里是day1最后一题。

描述

小 B 最近迷上了华容道,可是他总是要花很长的时间才能完成一次。于是,他想到用编程来完成华容道:给定一种局面,华容道是否根本就无法完成,如果能完成,最少需要多少时间。
小 B 玩的华容道与经典的华容道游戏略有不同,游戏规则是这样的:
在一个 n*m 棋盘上有 n*m 个格子,其中有且只有一个格子是空白的,其余 n*m-1个格子上每个格子上有一个棋子,每个棋子的大小都是 1*1 的;
有些棋子是固定的,有些棋子则是可以移动的;
任何与空白的格子相邻(有公共的边)的格子上的棋子都可以移动到空白格子上。 游戏的目的是把某个指定位置可以活动的棋子移动到目标位置。
给定一个棋盘,游戏可以玩 q 次,当然,每次棋盘上固定的格子是不会变的,但是棋盘上空白的格子的初始位置、指定的可移动的棋子的初始位置和目标位置却可能不同。第 i 次玩的时候,空白的格子在第 EXiEXi 行第 EYiEYi 列,指定的可移动棋子的初始位置为第 SXiSXi 行第 SYiSYi 列,目标位置为第 TXiTXi 行第 TYiTYi 列。
假设小 B 每秒钟能进行一次移动棋子的操作,而其他操作的时间都可以忽略不计。请你告诉小 B 每一次游戏所需要的最少时间,或者告诉他不可能完成游戏。

输入格式

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

输出格式

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

样例

样例输入

3 4 2
0 1 1 1
0 1 1 0
0 1 0 0
3 2 1 2 2 2
1 2 2 2 3 2

样例输出

2
-1

样例说明

棋盘上划叉的格子是固定的,红色格子是目标位置,圆圈表示棋子,其中绿色圆圈表示目标棋子。
第一次游戏,空白格子的初始位置是 (3, 2)(图中空白所示),游戏的目标是将初始位置在(1, 2)上的棋子(图中绿色圆圈所代表的棋子)移动到目标位置(2, 2)(图中红色的格子)上。
移动过程如下:
这里写图片描述
第二次游戏,空白格子的初始位置是(1, 2)(图中空白所示),游戏的目标是将初始位置在(2, 2)上的棋子(图中绿色圆圈所示)移动到目标位置 (3, 2)上。
这里写图片描述
要将指定块移入目标位置,必须先将空白块移入目标位置,空白块要移动到目标位置,必然是从位置(2,2)上与当前图中目标位置上的棋子交换位置,之后能与空白块交换位置的只有当前图中目标位置上的那个棋子,因此目标棋子永远无法走到它的目标位置,游戏无法完成。

数据范围

对于 30%的数据,1 ≤ n, m ≤ 10,q = 1;
对于 60%的数据,1 ≤ n, m ≤ 30,q ≤ 10;
对于 100%的数据,1 ≤ n, m ≤ 30,q ≤ 500。

来源

NOIP 2013 提高组 day 2


这个题就是搜索,问题就是怎么搜,一来写不来,直接写了个A*+hash+乱搞,写了很长,结果WA了85%。。。
然后听说直接bfs可以过60%,又加了个并查集优化,还是只过了60%。
然后。。。
就做不出来了
那么我们来一步一步分析。

首先,无论起始空白格和指定棋子在哪里,我们必须要将空白格移动到指定棋子四周。

这里写图片描述
然后,常规的搜索是移动空白格,这样会有810000种状态,一次dfs最坏就是8100000次,再dfs500次,肯定爆,我们就想,能不能直接移动指定棋子呢?
因每次移动指定棋子都需要空白在它四周,所以我们就将状态转换为300*4种,及用dis[i][j][k]表示指定棋子在(i,j),空白在它的k方向时的最小步数

然后就是怎么移动,比如我们要将下面的点右移一格可以这样做

这里写图片描述

先将空格移动到目标方向,再交换空格与目标棋子,因此我们可以建立个数组step[i][j][k][p],表示指定棋子在(i,j)要将空白从k方向移动到p方向需要的步数
找这个步数可以一来就先处理,只处理一遍,500个询问都可以用。
处理可以用bfs搜索,将(i,j)变为墙再bfs就可以了。

然后就是spfa,移动目标棋子,不断扩展就可以了,递推式如下:

if(dis[x][y][i^1]>dis[u.x][u.y][u.k]+step[u.x][u.y][u.k][i]+1)
                {
                    dis[x][y][i^1]=dis[u.x][u.y][u.k]+step[u.x][u.y][u.k][i]+1;
                    if(!vis[x][y][i^1])
                    {
                        Q.push(note(x,y,i^1));
                        vis[x][y][i^1]=1;
                    }
                }

有人会问,异或1是什么意思
这里用异或1来表示反方向,因为移动后,空白将变换为目标节点的反方向,可以参考上面的图。
同时我们这样来设计增量数组

const int T[2][4]={{0,0,-1,1},
                   {-1,1,0,0}};

就可以保证异或1是反方向了。
ps:u是结构体,可在下面的全代码中查看。
这就是基本思路了
代码如下,不难,没有加注释,相信大家看得懂,看不懂也可以提出来。

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<queue>
#define INF 2147480000
#define M 31

using namespace std;

const int T[2][4]={{0,0,-1,1},
                   {-1,1,0,0}};

int step[M][M][4][4],dis[M][M][4];
int n,m,q,map[M][M],vis[M][M][4],f[M][M];
int ex,ey,sx,sy,tx,ty;

struct dot
{
    int x,y;
    dot(int _x=0,int _y=0):x(_x),y(_y){}
};

struct note
{
    int x,y,k;
    note(int _x=0,int _y=0,int _k=0):x(_x),y(_y),k(_k){}
};

int bfs(int x1,int y1,int x2,int y2)
{
    if(!map[x1][y1]||!map[x2][y2])return INF;
    if(x1==x2&&y1==y2)return 0;
    queue<dot>Q;
    while(!Q.empty())Q.pop();
    for(int i=1;i<=n;i++)
        for(int j=1;j<=m;j++)
            f[i][j]=INF;
    Q.push(dot(x1,y1));
    f[x1][y1]=0;
    while(!Q.empty())
    {
        dot u=Q.front();
        Q.pop();
        if(f[x2][y2]!=INF)return f[x2][y2];
        for(int i=0;i<=3;i++)
        {
            int x=u.x+T[0][i],y=u.y+T[1][i];
            if(x>=1&&x<=n&&y>=1&&y<=m&&map[x][y]&&f[x][y]==INF)
            {
                f[x][y]=f[u.x][u.y]+1;
                Q.push(dot(x,y));
            }
        }

    }
    return INF;
}

void get_step()
{
    for(int i=1;i<=n;i++)
        for(int j=1;j<=m;j++)
        {
            int t=map[i][j];
            map[i][j]=0;
            for(int k=0;k<=3;k++)
                for(int p=0;p<=3;p++)
                    if(k==p)step[i][j][k][p]=0;
                    else step[i][j][k][p]=bfs(i+T[0][k],j+T[1][k],i+T[0][p],j+T[1][p]);
            map[i][j]=t;
        }
}

int spfa()
{
    memset(vis,0,sizeof(vis));
    queue<note>Q;
    while(!Q.empty())Q.pop();
    for(int i=0;i<=3;i++)
        if(dis[sx][sy][i]!=INF)
        {
            Q.push(note(sx,sy,i));
            vis[sx][sy][i]=1;
        }
    while(!Q.empty())
    {
        note u=Q.front();Q.pop();
        vis[u.x][u.y][u.k]=0;
        for(int i=0;i<=3;i++)
        {
            int x=u.x+T[0][i],y=u.y+T[1][i];
            if(x>=1&&x<=n&&y>=1&&y<=m&&map[x][y]&&step[u.x][u.y][u.k][i]!=INF)
                if(dis[x][y][i^1]>dis[u.x][u.y][u.k]+step[u.x][u.y][u.k][i]+1)
                {
                    dis[x][y][i^1]=dis[u.x][u.y][u.k]+step[u.x][u.y][u.k][i]+1;
                    if(!vis[x][y][i^1])
                    {
                        Q.push(note(x,y,i^1));
                        vis[x][y][i^1]=1;
                    }
                }
        }
    }
    int ans=INF;
    for(int i=0;i<=3;i++)
        ans=min(ans,dis[tx][ty][i]);
    if(ans==INF)ans=-1;
    return ans;
}

int work()
{
    cin>>ex>>ey>>sx>>sy>>tx>>ty;
    if(sx==tx&&sy==ty)return 0;
    for(int i=1;i<=n;i++)
        for(int j=1;j<=m;j++)
            for(int k=0;k<=3;k++)
                dis[i][j][k]=INF;
    map[sx][sy]=0;
    for(int i=0;i<=3;i++)
        dis[sx][sy][i]=bfs(ex,ey,sx+T[0][i],sy+T[1][i]);
    map[sx][sy]=1;
    return spfa();
}

int main()
{
    #ifdef LOCAL
    freopen("puzzle.in","r",stdin);
    freopen("puzzle.out","w",stdout);
    #else
    #endif
    cin>>n>>m>>q;
    for(int i=1;i<=n;i++)
        for(int j=1;j<=m;j++)
            scanf("%d",&map[i][j]);
    get_step();
    for(int i=1;i<=q;i++)
        cout<<work()<<'\n';

    return 0;
}

如果有什么问题,或错误,请在评论区提出,谢谢。

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值