题目描述
【问题描述】
小 B 最近迷上了华容道,可是他总是要花很长的时间才能完成一次。于是,他想到用编程来完成华容道:给定一种局面, 华容道是否根本就无法完成,如果能完成, 最少需要多少时间。
小 B 玩的华容道与经典的华容道游戏略有不同,游戏规则是这样的:
-
在一个 n*m 棋盘上有 n*m 个格子,其中有且只有一个格子是空白的,其余 n*m-1个格子上每个格子上有一个棋子,每个棋子的大小都是 1*1 的;
-
有些棋子是固定的,有些棋子则是可以移动的;
- 任何与空白的格子相邻(有公共的边)的格子上的棋子都可以移动到空白格子上。
游戏的目的是把某个指定位置可以活动的棋子移动到目标位置。
给定一个棋盘,游戏可以玩 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。
输入输出样例
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。
70分暴力
第一想法:哈,不会做。
然而,数据有梯度,不要放弃梦想。
我们可以开一个结构体,存储需要移动的棋子位置和空格的位置,只有两种更新方法:
- 移动空格,空格不与需要移动的棋子重合。
- 空格移动一步可以到达需要移动的棋子,两者交换位置。
然而第一次,忘了每次清空队列,只有30分。
太暴力了,但可以拿70分(高性价比)。
#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdio>
#include<queue>
using namespace std;
int n,m,que,ex,ey,sx,sy,tx,ty,xx[]={0,0,1,0,-1},yy[]={0,-1,0,1,0};
bool b[35][35],vis[35][35][35][35];
struct node
{
int x1,y1,x2,y2,dis;
}tmp;
queue<node>q;
void bfs()
{
while(!q.empty())
q.pop();
tmp.x1=sx,tmp.y1=sy,tmp.x2=ex,tmp.y2=ey,tmp.dis=0;
vis[sx][sy][ex][ey]=1;
q.push(tmp);
while(!q.empty())
{
node u=q.front();
q.pop();
if(u.x1==tx&&u.y1==ty)
{
printf("%d\n",u.dis);
return ;
}
for(int i=1;i<=4;i++)//移动空格
if(u.x2+xx[i]>=1&&u.x2+xx[i]<=n&&u.y2+yy[i]>=1&&u.y2+yy[i]<=m&&!vis[u.x1][u.y1][u.x2+xx[i]][u.y2+yy[i]]&&!b[u.x2+xx[i]][u.y2+yy[i]]&&(u.x1!=u.x2+xx[i]||u.y1!=u.y2+yy[i]))
{
tmp=u;
tmp.x2+=xx[i],tmp.y2+=yy[i],tmp.dis++;
vis[tmp.x1][tmp.y1][tmp.x2][tmp.y2]=1;
q.push(tmp);
}
for(int i=1;i<=4;i++)//交换
if(u.x1+xx[i]==u.x2&&u.y1+yy[i]==u.y2&&!vis[u.x2][u.y2][u.x1][u.y1])
{
tmp=u;
swap(tmp.x1,tmp.x2),swap(tmp.y1,tmp.y2);
tmp.dis++;
vis[tmp.x1][tmp.y1][tmp.x2][tmp.y2]=1;
q.push(tmp);
}
}
printf("-1\n");
return ;
}
int main()
{
scanf("%d%d%d",&n,&m,&que);
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
{
int x;
scanf("%d",&x);
if(x==0)
b[i][j]=1;
}
while(que--)
{
scanf("%d%d%d%d%d%d",&ex,&ey,&sx,&sy,&tx,&ty);
memset(vis,0,sizeof(vis));
bfs();
}
return 0;
}
正解
下面说正解:
- 移动空格,空格不与需要移动的棋子重合。
- 空格移动一步可以到达需要移动的棋子,两者交换位置
- 注意判断s==t的情况,会坑35分。
- 数组大小一定要开够,对于新建的图,需要n*m*4+2*q各点,n*m*(4*4+4)+2*4*q条边。
#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdio>
#include<queue>
using namespace std;
int n,m,que,mcnt,cnt,hd[4605],mp[35][35],id[35][35][5],dis[4605],xx[]={0,0,1,0,-1},yy[]={0,-1,0,1,0};
bool vis[35][35],inq[4605];
struct edge
{
int nxt,to,val;
}v[22005];
struct node
{
int x,y,dis;
}tmp;
queue<node>q1;
queue<int>q2;
void addedge(int x,int y,int z)
{
++cnt;
v[cnt].to=y;
v[cnt].nxt=hd[x];
v[cnt].val=z;
hd[x]=cnt;
}
bool pd(int x,int y)
{
if(x>=1&&x<=n&&y>=1&&y<=m&&mp[x][y]==1)
return 1;
return 0;
}
int bfs(int banx,int bany,int sx,int sy,int tx,int ty)
{
while(!q1.empty())
q1.pop();
memset(vis,0,sizeof(vis));
tmp.x=sx,tmp.y=sy,tmp.dis=0;
vis[sx][sy]=1,vis[banx][bany]=1;
q1.push(tmp);
while(!q1.empty())
{
node u=q1.front();
q1.pop();
if(u.x==tx&&u.y==ty)
return u.dis;
for(int i=1;i<=4;i++)
if(pd(u.x+xx[i],u.y+yy[i])&&!vis[u.x+xx[i]][u.y+yy[i]])
{
tmp=u;
tmp.x+=xx[i],tmp.y+=yy[i],tmp.dis++;
vis[tmp.x][tmp.y]=1;
q1.push(tmp);
}
}
return 1e9+11;
}
void spfa(int S,int T)
{
memset(dis,0x7f,sizeof(dis));
memset(inq,0,sizeof(inq));
inq[S]=1;
dis[S]=0;
q2.push(S);
while(!q2.empty())
{
int u=q2.front();
q2.pop();
inq[u]=0;
for(int i=hd[u];i;i=v[i].nxt)
if(dis[v[i].to]>dis[u]+v[i].val)
{
dis[v[i].to]=dis[u]+v[i].val;
if(!inq[v[i].to])
{
inq[v[i].to]=1;
q2.push(v[i].to);
}
}
}
if(dis[T]>1e9+7)
printf("-1\n");
else
printf("%d\n",dis[T]);
}
int main()
{
scanf("%d%d%d",&n,&m,&que);
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
scanf("%d",&mp[i][j]);
//编号
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
for(int k=1;k<=4;k++)
id[i][j][k]=++mcnt;
//预处理(x,y)从k方向移到g方向连边
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
if(mp[i][j])
for(int k=1;k<=4;k++)
if(pd(i+xx[k],j+yy[k]))
for(int g=1;g<=4;g++)
if(k!=g&&pd(i+xx[g],j+yy[g]))
{
int t=bfs(i,j,i+xx[k],j+yy[k],i+xx[g],j+yy[g]);
if(t<=1e9+7)
addedge(id[i][j][k],id[i][j][g],t);
}
//预处理(x,y) 在k方向交换,连边
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
if(mp[i][j])
for(int k=1;k<=4;k++)
if(pd(i+xx[k],j+yy[k]))
addedge(id[i][j][k],id[i+xx[k]][j+yy[k]][(k+1)%4+1],1);
while(que--)
{
int ex,ey,sx,sy,tx,ty;
scanf("%d%d%d%d%d%d",&ex,&ey,&sx,&sy,&tx,&ty);
if(sx==tx&&sy==ty)
{
printf("0\n");
continue;
}
int S=++mcnt,T=++mcnt;
for(int i=1;i<=4;i++)
if(pd(sx+xx[i],sy+yy[i]))
addedge(S,id[sx][sy][i],bfs(sx,sy,ex,ey,sx+xx[i],sy+yy[i]));
for(int i=1;i<=4;i++)
if(pd(tx+xx[i],ty+yy[i]))
addedge(id[tx][ty][i],T,0);
spfa(S,T);
}
return 0;
}