【NOIP2013】华容道

题目描述

【问题描述】

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

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

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

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

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

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

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

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

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

输入输出格式

输入格式:

输入文件为 puzzle.in。

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

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

输出格式:

输出文件名为 puzzle.out。

输出有 q 行,每行包含 1 个整数,表示每次游戏所需要的最少时间,如果某次游戏无法完成目标则输出−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
输出样例#1:
2
-1

说明

【输入输出样例说明】

棋盘上划叉的格子是固定的,红色格子是目标位置,圆圈表示棋子,其中绿色圆圈表示目标棋子。

  1. 第一次游戏,空白格子的初始位置是 (3, 2)(图中空白所示),游戏的目标是将初始位置在(1, 2)上的棋子(图中绿色圆圈所代表的棋子)移动到目标位置(2, 2)(图中红色的格子)上。

移动过程如下:

  1. 第二次游戏,空白格子的初始位置是(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。

题解:

  将正解之前,我想先说一下80分暴力,是一个十分好想的状态,每个局面其实就只是和空白和目标棋子的位置有关,那么我们很简单就可以设出一个状态b[x][y][z][d]分别表示空白在(x,y),目标棋子在(z,d)的最小花费,当然如果是bfs拓展状态那么就只要用bool判重就可以了,因为第一次一定是最优的。

  当然用这个发现状态数是n^4的转移是O1的,然而不幸的是他有500组数据,刚刚好被卡住了。怎么办?

  发现其实很多状态其实是没有用的,你不可能空白棋子向着目标棋子的反方向走,注意到真正使得目标棋子移动时,空白棋子都在旁边,也就是只有这些状态是有效的,其他的都没必要拓展,也就是真正有用的状态是n^2级别的,那么怎么只拓展每个有用的状态呢?

  我们可以对于每个格子,我们要预处理出求从他的一侧跑到另外一侧的最短路,这个跑n*n*4遍spfa就可以了,这个不会超时,边是O(n)级别的。有了这个有什么用呢?因为之后每个关键点都是空白棋子贴着这个目标棋子,所以我们要目标棋子移动一格的代价就是让空白棋子跑到你想让他移动的那一侧的代价+1(空白棋子和目标棋子交换的代价),然后,边权有了,跑最短路就可以了。

  这里我说明一下,因为我没看题解,然后看tag的时候有一个bfs,所以我最短路是用bfs求的,常数超大,20个点一共跑了4秒多,这个代码可以看我的,如果想跑得更快,把bfs改成spfa就可以了。

代码:(有点长,但大多都是相同的东西复制粘贴)

#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
#include <cmath>
#include <iostream>
#include <queue>
#define MAXN 32
#define RG register
#define ll long long
using namespace std;
bool mp[MAXN][MAXN];
ll b[MAXN][MAXN][MAXN][MAXN];
int n,m,qq,flag=0;
int addx[5]={0,1,-1,0,0},addy[5]={0,0,0,1,-1};
ll  dis[MAXN][MAXN];
bool have[MAXN][MAXN];
ll w[MAXN][MAXN][5][5];
ll inf,ans;
struct node{
    int nx,ny,wx,wy; ll tot; int step;
};
int ex,ey;
queue<node> q;
queue<int> s[2];
 
inline bool check(int x,int y){
    if(!mp[x][y]) return 0;
    if(x<=0||x>n) return 0;
    if(y<=0||y>m) return 0;
    return 1;
}
 
void spfa(int starx,int stary,int x,int y){
    while(!s[0].empty()) s[0].pop();
    while(!s[1].empty()) s[1].pop();
    memset(have,0,sizeof(have));
    memset(dis,127,sizeof(dis));inf=dis[0][0];
    dis[starx][stary]=0;
    s[0].push(starx),s[1].push(stary);
    have[starx][stary]=1;
    while(!s[0].empty()){
        int nowx=s[0].front(),nowy=s[1].front();
        s[0].pop(),s[1].pop();
        have[nowx][nowy]=0;
        for(int i=1;i<=4;i++){
            int tox=nowx+addx[i],toy=nowy+addy[i];
            if(tox==x&&toy==y) continue;
            if(!check(tox,toy)) continue;
            if(dis[tox][toy]>dis[nowx][nowy]+1){
                dis[tox][toy]=dis[nowx][nowy]+1;
                if(!have[tox][toy]){
                    have[tox][toy]=1;
                    s[0].push(tox),s[1].push(toy);
                }
            }
        }
    }
}
 
int choose(int x,int y,int fx,int fy){
    if(fx==x-1&&fy==y) return 1;
    if(fx==x&&fy==y+1) return 2;
    if(fx==x+1&&fy==y) return 3;
    if(fx==x&&fy==y-1) return 4;
    return 0;
}
 
void bfs(int sx,int sy,int d,int z){
    while(!q.empty()) q.pop();
    memset(b,127,sizeof(b));
    q.push((node){sx,sy,d,z,0,1});
    while(!q.empty()){
        int nowx=q.front().nx,nowy=q.front().ny,fx=q.front().wx,fy=q.front().wy;ll tot=q.front().tot; int step=q.front().step;
        q.pop();
        if(nowx==ex&&nowy==ey){
            ans=min(ans,tot);
            flag=1;
        }
        if(tot>=b[nowx][nowy][fx][fy]) continue;
        b[nowx][nowy][fx][fy]=min(b[nowx][nowy][fx][fy],tot);
        for(int i=1;i<=4;i++){
            int tox=nowx+addx[i],toy=nowy+addy[i];
            if(!check(tox,toy)) continue;
            int id1=choose(nowx,nowy,fx,fy),id2=choose(nowx,nowy,tox,toy);
            if(id1!=0){
                if(w[nowx][nowy][id1][id2]==inf) continue;
                q.push((node){tox,toy,nowx,nowy,tot+w[nowx][nowy][id1][id2]+1,step+1});
            }
            else{
                spfa(fx,fy,nowx,nowy);
                if(dis[tox][toy]==inf) continue;
                q.push((node){tox,toy,nowx,nowy,tot+dis[tox][toy]+1,step+1});
            }
        }
    }
}
 
void pre(){
    memset(w,127,sizeof(w));
    for(int i=1;i<=n;i++)
    for(int j=1;j<=m;j++){
        int x,y;
        x=i-1,y=j;
        if(check(x,y)){
            spfa(x,y,i,j);
            w[i][j][1][1]=dis[i-1][j];
            w[i][j][1][2]=dis[i][j+1];
            w[i][j][1][3]=dis[i+1][j];
            w[i][j][1][4]=dis[i][j-1];
        }
        x=i,y=j+1;
        if(check(x,y)){
            spfa(x,y,i,j);
            w[i][j][2][1]=dis[i-1][j];
            w[i][j][2][2]=dis[i][j+1];
            w[i][j][2][3]=dis[i+1][j];
            w[i][j][2][4]=dis[i][j-1];
        }
        x=i+1,y=j;
        if(check(x,y)){
            spfa(x,y,i,j);
            w[i][j][3][1]=dis[i-1][j];
            w[i][j][3][2]=dis[i][j+1];
            w[i][j][3][3]=dis[i+1][j];
            w[i][j][3][4]=dis[i][j-1];
        }
        x=i,y=j-1;
        if(check(x,y)){
            spfa(x,y,i,j);
            w[i][j][4][1]=dis[i-1][j];
            w[i][j][4][2]=dis[i][j+1];
            w[i][j][4][3]=dis[i+1][j];
            w[i][j][4][4]=dis[i][j-1];
        }
    }
}
 
int main()
{
    scanf("%d%d%d",&n,&m,&qq);
    for(int i=1;i<=n;i++)
    for(int j=1;j<=m;j++) cin>>mp[i][j];
    pre();
    while(qq--){
        int wx,wy,sx,sy;
        scanf("%d%d%d%d%d%d",&wx,&wy,&sx,&sy,&ex,&ey);
        if(sx==ex&&sy==ey){
            printf("0
");continue;
        }
        ans=inf;
        bfs(sx,sy,wx,wy);
        if(!flag) printf("-1
");
        else printf("%lld
",ans);
        flag=0;
    }
    return 0;
}
原文地址:https://www.cnblogs.com/renjianshige/p/7551307.html