问题描述
如下面第一个图的九宫格中,放着 1~8 的数字卡片,还有一个格子空着。与空格子相邻的格子中的卡片可以移动到空格中。经过若干次移动,可以形成第二个图所示的局面。
我们把第一个图的局面记为:12345678.
把第二个图的局面记为:123.46758
显然是按从上到下,从左到右的顺序记录数字,空格记为句点。
本题目的任务是已知九宫的初态和终态,求最少经过多少步的移动可以到达。如果无论多少步都无法到达,则输出-1。
输入格式
输入第一行包含九宫的初态,第二行包含九宫的终态。
输出格式
输出最少的步数,如果不存在方案,则输出-1。
样例输入
12345678.
123.46758
样例输出
3
样例输入
13524678.
46758123.
样例输出
22
解题思路:采用类似于迷宫老鼠求最短路问题,利用BFS找所有的情况,关键问题在于判重,我们知道一共有9!种可能,利用康拓展开,就可以确定每种排列位于所有排列中的位置。
康拓展开
定义:X=an*(n-1)!+an-1*(n-2)!+…+ai*(i-1)!+…+a21!+a10!
2的后面有1个数比2小,即 1 * 2!
1的后面没有比1小的数,即 0 * 1!
3的后面没有比3小的数,即 0 * 0!
X = 12!+01!+0*0! = 2
这样我们声明一个数组st[9!]来记录哪个状态被访问过了;同时我们声明一个二维数组rec[9!][9]来表示某个状态的排列是什么。
代码如下
#include <iostream>
#include <queue>
using namespace std;
int jing[4][4] = {0};
int dir[4][2] = {{-1,0},{1,0},{0,-1},{0,1}};
struct poi{ //记录0在二维表中的位置,以及当前排列的状态(即所有排列中的位置),以及到这个位置的步数
int x; //横坐标
int y;//纵坐标
int state;//排列状态
int step = 0;//到该的步数
poi(int xx,int yy,int st,int ste){
x = xx;
y = yy;
state = st;
step = ste;
}
};
queue<poi>que; //维护一个队列
int rec[362880][9];//记录所有状态对应的排列
int st[362880] = {0}; //记录那个状态被访问过了
int kangtuo(int x[]) //康拓展开求位置
{
int fac[]={1,1,2,6,24,120,720,5040,40320};
int i,j,t,sum;
sum = 0;
for(i=0;i<9;i++)
{
t = 0;
for(j=i+1;j<9;j++)
{
if(x[j]<x[i])
t++;
}
sum = sum+t*fac[8-i];
}
return sum;
}
poi des(1,1,1,0); //目标排列,参数随便写的,后面会赋值
int bfs(int tx,int ty,int sta){ //起始位置tx,ty以及起始状态
que.push(poi(tx,ty,sta,0)); //压入第一个位置
while(!que.empty()){
// cout<<que.size()<<endl;
poi tp = que.front(); //取出一个位置
int x = tp.x;
int y = tp.y;
que.pop();
int*tarr = rec[tp.state];
for(int i = 1;i <= 3;i++){ // 把当前排列状态所对应的一维排列转换成二维的
for(int j = 1;j <= 3;j++){
jing[i][j] = tarr[3*(i - 1) + j - 1];
}
}
if(tp.state == des.state){ //当前状态即为目标状态,找到了
return tp.step;
}
for(int i = 0;i < 4;i++){ //像四个方向移动
x = x + dir[i][0];
y = y + dir[i][1];
if(x >= 1 && x <= 3 && y >= 1 && y <= 3){ //越界判断
//移动#############
int t = jing[x][y];
jing[x][y] = jing[x - dir[i][0]][y - dir[i][1]];
jing[x - dir[i][0]][y - dir[i][1]] = t;
///移动########################
int tarr[9];
for(int i = 1; i <= 3;i++){
for(int j = 1;j <= 3;j++){
tarr[3*(i - 1) + j - 1] = jing[i][j];
}
}
int stt = kangtuo(tarr);
//装换成状态
if(!st[stt]){ //此状态没有被访问过
que.push(poi(x,y,stt,tp.step + 1));//压入新的状态,步数为上一状态步数+1
st[stt] = 1;
for(int i = 0;i < 9;i++){
rec[stt][i] = tarr[i];
}
}
//恢复移动################
t = jing[x][y];
jing[x][y] = jing[x - dir[i][0]][y - dir[i][1]];
jing[x - dir[i][0]][y - dir[i][1]] = t;
//恢复移动###############
}
x = x - dir[i][0];
y = y - dir[i][1];
}
}
return -1;
}
int main(){
string s1;
string s2;
cin>>s1;
cin>>s2;
int sa[9] = {0};
int sb[9] = {0};
int start = 0;
for(int i = 0;i < 9;i++){
if(s1[i] == '.'){ //‘.’不处理,视为0
start = i;
}else{
sa[i] = s1[i] - '0';
}
if(s2[i] != '.'){
sb[i] = s2[i] - '0';
}
}
int st1 = kangtuo(sa); //起始排列状态
int st2 = kangtuo(sb); //目标排列状态
des.state = st2;
for(int i = 0;i < 9;i++){ //把起始排列按状态存入rec中
rec[st1][i] = sa[i];
}
st[st1] = 1; //认为起始状态被访问过了
cout<<bfs(start / 3 + 1,start % 3 + 1,st1);
return 0;
}