问题描述
在3*3的九宫格棋盘中,摆有8个将牌,每个将牌上刻有1-8中的某一个数码。棋盘中留有一个空格,允许其四周的某一个将牌向空格移动,这样通过移动将牌就可以不断改变布局。给定一种初始布局,求变为目标布局所需的最少步数。
目标布局为:
1 2 3
8 0 4
7 6 5
其中0表示空格。
输入格式
三行共9个数,表示初始布局
输出格式
一个整数表示变为目标布局的最少步数。若无解输出“no solution”
样例输入
0 1 3
8 2 4
7 6 5
样例输出
2
题解
从初始布局开始广搜。
关键是状态的保存和判重比较麻烦。
直接用3*3的形式保存肯定会爆的,考虑到一个状态只有九个数,可以把这九个数压缩成一个九位数,保存的问题就解决了。一个状态有九个格子,每个格子填一个数有9种填法(0~8),九个格子一共9!种填法,不超过4e5,队列开4e5就够了。
接下来,怎么判重呢?如果每得出一个状态就和前面所有状态比较一遍,显然会TLE;如果用一个布尔数组来记录每种状态是否出现过,也要开876543210的数组,直接MLE了。由于最多只有9!种状态,其实只要标记这9!种状态有没有出现过就可以了。怎么把876543210压缩成9!呢?哈希!找个大质数,判断每个状态的模有没有出现过就行了。
1e6+7是个很好的质数,本题用1e6+7作为哈希函数冲突不超过5个
1 #include <cstdio> 2 const int dx[10][5]={ {0,0,0,0,0},{2,2,4,0,0},{3,1,3,5,0}, 3 {2,2,6,0,0},{3,1,5,7,0},{4,2,4,6,8}, 4 {3,3,5,9,0},{2,4,8,0,0},{3,5,7,9,0}, 5 {2,6,8,0,0} 6 }; //从右下角开始从下到上从右到左给九个格子编号,dx[i][0]表示和第i个格子相邻的格子的个数,dx[i][j](j>0)表示和第i个格子相邻的格子的编号 7 const int ds[10]={1,10,100,1000,10000,1e5,1e6,1e7,1e8,1e9}; 8 const int inf=1e6+7,m=123804765; 9 int f[1000005][5],q[400005][2],n; 10 bool check(int x) 11 { 12 int s=x%inf; 13 if (!f[s][0]) 14 { 15 f[s][++f[s][0]]=x; 16 return 1; 17 } 18 for (int i=1;i<=f[s][0];i++) 19 if (f[s][i]==x) 20 return 0; 21 f[s][++f[s][0]]=x; 22 return 1; 23 } 24 int getzero(int x) 25 { 26 int i,pre=x%10,s; 27 if (!pre) return 1; 28 for (i=2;i<=9;i++) 29 { 30 s=x%ds[i]; 31 if (s==pre) return i; 32 pre=s; 33 } 34 return 9; 35 } 36 int bfs() 37 { 38 if (n==m) return 0; 39 int h=0,t=1,i,j,k,x,y,s,u; 40 q[1][1]=n; 41 check(n); 42 while (h!=t) 43 { 44 h++; 45 u=q[h][1]; 46 x=getzero(u); 47 for (i=1;i<=dx[x][0];i++) 48 { 49 j=dx[x][i]; 50 y=(u/ds[j-1])%10; 51 s=u-y*ds[j-1]+y*ds[x-1]; 52 if (check(s)) 53 { 54 if (s==m) return q[h][0]+1; 55 q[++t][1]=s; 56 q[t][0]=q[h][0]+1; 57 58 } 59 } 60 61 } 62 return -1; 63 } 64 int main() 65 { 66 int i,j,x; 67 /*for (i=1;i<=9;i++) 68 scanf("%d",&x), 69 n=n*10+x;*/ 70 scanf("%d",&n); 71 int ans=bfs(); 72 if (ans!=-1) printf("%d",ans); 73 else printf("no solution"); 74 return 0; 75 }