Eight
經典的八數碼問題,賽前重溫順便記錄一下。
傳送門
題意:3*3的方陣,由1-8的數字佔據每一個格子,還有一個空格。可以通過空格改變方陣中數字的位置。輸入一個方陣,求變爲123/456/78的樣式的移動步驟。
思路:
移動空格周遭的數字,其實等價於移動空格。
於是,我們把空格的位置作爲狀態。原本二維的位置轉換成一維位置進行記錄。
逆向思維,從目標狀態開始BFS,每到一個新狀態就記錄父親狀態到新狀態的移動方式,和父親狀態。這樣就可以通過狀態轉移來得到路徑。
最重要的問題是如何判重。
因爲這裏有9個數字,我們不可能說開一個的數組來判重,這樣鐵MLE。聰明的先輩們想到了一個方案:康託展開。
康託展開就是,通過數學手段,把一個數字排序,如123456789,轉換爲這個數字排序在9個數的全排序中的序號,在這個例子中是1。
序號最多也就個,因此,只需要開大小爲以上的一維數組便能進行狀態判重。
代碼:
注:有所參考
#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");
}
}