九宫重拍(bfs + 康拓展开)
问题描述
如下面第一个图的九宫格中,放着 1~8 的数字卡片,还有一个格子空着。与空格子相邻的格子中的卡片可以移动到空格中。经过若干次移动,可以形成第二个图所示的局面。
我们把第一个图的局面记为:12345678.
把第二个图的局面记为:123.46758
显然是按从上到下,从左到右的顺序记录数字,空格记为句点。
本题目的任务是已知九宫的初态和终态,求最少经过多少步的移动可以到达。如果无论多少步都无法到达,则输出-1。
我们把第一个图的局面记为:12345678.
把第二个图的局面记为:123.46758
显然是按从上到下,从左到右的顺序记录数字,空格记为句点。
本题目的任务是已知九宫的初态和终态,求最少经过多少步的移动可以到达。如果无论多少步都无法到达,则输出-1。
输入格式
输入第一行包含九宫的初态,第二行包含九宫的终态。
输出格式
输出最少的步数,如果不存在方案,则输出-1。
样例输入
12345678.
123.46758
123.46758
样例输出
3
样例输入
13524678.
46758123.
46758123.
样例输出
22
==========================分割线==================================
这个题刚开始直接用bfs去做,但是开的标记数组的维数会非常高,后来才在网上看到用康拓展开,能用到康拓展开是因为这可以看成是一个序列,所以可以用康拓展开求出他在全排列中的次序,这样标记数组就可以开一维的了,这道题的广搜和三个水杯那个题差不多
代码如下:
1 #include<iostream> 2 #include <cstdio> 3 #include <queue> 4 #include <cstring> 5 using namespace std; 6 typedef long long LL; 7 struct Node{ 8 int cur[9]; 9 LL step; 10 }; 11 Node s, e; 12 const int N = 1e6; 13 const int Next[4][2] = {{1, 0}, {0, 1}, {-1, 0}, {0, -1}};//搜索的四个方向 14 bool vis[N * 4];//标记数组 15 int fac[] = {1, 1, 2, 6, 24, 120, 720, 5040, 40320, 362880};//前几个数的阶乘 16 LL cantor(int s[])//康拓展开 17 { 18 LL ans = 0; 19 int n = 9; 20 for (int i = 0; i < n - 1; i++) 21 { 22 int tmp = 0; 23 for (int j = i + 1; j < n; j++) 24 if (s[j] < s[i]) 25 tmp++; 26 ans += fac[n - i - 1] * tmp; 27 } 28 return ans; 29 } 30 void cantor_reverse(int index, int a[])//康拓展开逆, 在本道题中未使用 31 { 32 index--; 33 int n = 9; 34 bool visit[9]; 35 memset(visit, false, sizeof(visit)); 36 for (int i = 0; i < n; i++) 37 { 38 int tmp = index / fac[n - i - 1]; 39 for (int j = 0; j <= tmp; j++) 40 if (visit[j]) 41 tmp++; 42 a[i] = tmp + 1; 43 visit[tmp] = true; 44 index %= fac[n - i - 1]; 45 } 46 } 47 bool ischecked(int row, int col)//检查是否满足移动的条件 48 { 49 return (row > 0 && col > 0 && row < 4 && col < 4); 50 } 51 bool matched(Node node)//看是否达到给定的状态 52 { 53 for (int i = 0; i < 9; i++) 54 if (node.cur[i] != e.cur[i]) 55 return false; 56 return true; 57 } 58 LL bfs() 59 { 60 memset(vis, false, sizeof(vis)); 61 queue<Node> Q; 62 s.step = 0; 63 Q.push(s); 64 Node p, q; 65 int start_num = cantor(s.cur); 66 vis[start_num] = true;//标记第一个元素 67 while (!Q.empty()) 68 { 69 p = Q.front(); 70 Q.pop(); 71 int pos; 72 for (pos = 0; pos < 9; pos++) 73 if (p.cur[pos] == 9)//将"."当成9来计算 74 break; 75 int row, col, new_row, new_col; 76 row = pos / 3 + 1; 77 col = pos % 3 + 1; 78 for (int i = 0; i < 4; i++) 79 { 80 new_row = row + Next[i][0]; 81 new_col = col + Next[i][1]; 82 if (ischecked(new_row, new_col))//判断是否满足可移动的条件 83 { 84 q = p; 85 q.step = p.step + 1; 86 //下面三步是交换这两个数(也就是移动到空位去) 87 int t = q.cur[(row - 1) * 3 + col - 1]; 88 q.cur[(row - 1) * 3 + col - 1] = q.cur[(new_row - 1) * 3 + new_col - 1]; 89 q.cur[(new_row - 1) * 3 + new_col - 1] = t; 90 if (matched(q))//如果找到之后直接返回 91 { 92 return q.step; 93 } 94 int num = cantor(q.cur); 95 if (!vis[num]) 96 { 97 vis[num] = true; 98 Q.push(q); 99 } 100 } 101 } 102 } 103 return -1;//找不到就返回-1 104 } 105 int main() 106 { 107 char sta[10], en[10]; 108 scanf("%s %s", sta, en); 109 for (int i = 0; i < 9; i++) 110 if (sta[i] != '.') 111 s.cur[i] = sta[i] - '0'; 112 else 113 s.cur[i] = 9;//将'.'看成9 114 for (int i = 0; i < 9; i++) 115 if (en[i] != '.') 116 e.cur[i] = en[i] - '0'; 117 else 118 e.cur[i] = 9; 119 LL tmp = bfs(); 120 printf("%lld\n", tmp); 121 122 return 0; 123 }