Eight
经典的八数码问题,赛前重温顺便记录一下。
传送门
题意:3*3的方阵,由1-8的数字占据每一个格子,还有一个空格。可以通过空格改变方阵中数字的位置。输入一个方阵,求变为123/456/78的样式的移动步骤。
思路:
移动空格周遭的数字,其实等价于移动空格。
于是,我们把空格的位置作为状态。原本二维的位置转换成一维位置进行记录。
逆向思维,从目标状态开始BFS,每到一个新状态就记录父亲状态到新状态的移动方式,和父亲状态。这样就可以通过状态转移来得到路径。
最重要的问题是如何判重。
因为这里有9个数字,我们不可能说开一个
1
0
9
10^9
109的数组来判重,这样铁MLE。聪明的先辈们想到了一个方案:康托展开。
康托展开就是,通过数学手段,把一个数字排序,如123456789,转换为这个数字排序在9个数的全排序中的序号,在这个例子中是1。
序号最多也就
9
!
9!
9!个,因此,只需要开大小为
9
!
9!
9!以上的一维数组便能进行状态判重。
代码:
注:有所参考
#include<bits/stdc++.h>
using namespace std;
struct node1{
char path;//记录路径
int fa;//父节点
};
struct node2{//存状态
int aa[10];
int n,son;//n为9在aa中的位置
};
int dir[4][2]={{1,0},{-1,0},{0,1},{0,-1}},fac[10];
node1 Node[370000];//记录每个状态的单一路径和转移方式
//362880为全排列数目
void set_fac(){//计算0到8的阶乘
fac[0]=1;
for(int i=1;i<=8;i++)
fac[i]=fac[i-1]*i;
}
int cantor(int aa[]){//康托展开
int ans=0,k;
for(int i=0;i<9;i++){
k=0;
for(int j=i+1;j<9;j++)
if(aa[i]>aa[j])
k++;
ans+=k*fac[8-i];
}
return ans;
}
void bfs(int a[]){
queue<node2> Q;
node2 q,p;//q为当前点,p为下一点
for(int e=0;e<9;e++)
q.aa[e]=a[e];
q.n=8;q.son=0;
Node[q.son].fa=0;
Q.push(q);
while(!Q.empty()){
q=Q.front();Q.pop();
for(int e=0;e<4;e++){
p=q;
//9的坐标二维化
int tx=q.n%3+dir[e][0],ty=q.n/3+dir[e][1];
if(tx>=0&&ty>=0&&tx<3&&ty<3){
p.n=ty*3+tx;//一维化回去
int tem=p.aa[p.n];p.aa[p.n]=p.aa[q.n];p.aa[q.n]=tem;//把9的位置进行交换
p.son=cantor(p.aa);//把当前状态进行康托展开,得到排列序号
if(Node[p.son].fa==-1){//如果这个状态没有到过
Node[p.son].fa=q.son;//fa存上一个状态的排列序号
if(e==0) Node[p.son].path='l';//记录新路径
if(e==1) Node[p.son].path='r';
if(e==2) Node[p.son].path='u';
if(e==3) Node[p.son].path='d';
Q.push(p);
}
}
}
}
}
int main(){
int goal[10]={1,2,3,4,5,6,7,8,9};//终点状态
int start[10];//初始状态
for(int i=0;i<370000;i++)
Node[i].fa=-1;
set_fac();
bfs(goal);
char ch[50];
while(gets(ch)>0){
for(int i=0,j=0;ch[i]!='\0';i++){
if(ch[i]=='x')
start[j++]=9;
else if(ch[i]>='0'&&ch[i]<='8')
start[j++]=ch[i]-'0';
}
int s=cantor(start);
if(Node[s].fa==-1){
printf("unsolvable\n");
continue;
}
while(s!=0){
printf("%c",Node[s].path);
s=Node[s].fa;
}
printf("\n");
}
}