如下图所示,有一个
#
形的棋盘,上面有 1,2,31,2,3 三种数字各 88 个。给定 88 种操作,分别为图中的 A∼HA∼H。
这些操作会按照图中字母和箭头所指明的方向,把一条长为 77 的序列循环移动 11 个单位。
例如下图最左边的
#
形棋盘执行操作 AA 后,会变为下图中间的#
形棋盘,再执行操作 CC 后会变成下图最右边的#
形棋盘。给定一个初始状态,请使用最少的操作次数,使
#
形棋盘最中间的 88 个格子里的数字相同。输入格式
输入包含多组测试用例。
每个测试用例占一行,包含 2424 个数字,表示将初始棋盘中的每一个位置的数字,按整体从上到下,同行从左到右的顺序依次列出。
输入样例中的第一个测试用例,对应上图最左边棋盘的初始状态。
当输入只包含一个 00 的行时,表示输入终止。
输出格式
每个测试用例输出占两行。
第一行包含所有移动步骤,每步移动用大写字母 A∼HA∼H 中的一个表示,字母之间没有空格,如果不需要移动则输出
No moves needed
。第二行包含一个整数,表示移动完成后,中间 88 个格子里的数字。
如果有多种方案,则输出字典序最小的解决方案。
输入样例:
1 1 1 1 3 2 3 2 3 1 3 2 2 3 1 2 2 2 3 1 2 1 3 3 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 0
输出样例:
AC 2 DDHH 2
将原棋盘定义为如下坐标表示的棋盘,分别将操作A, B, C, D, E, F, G, H表示为数字0,1,2,3,4,5,6,7
A B
0 1
2 3
H 4 5 6 7 8 9 10 C
11 12
G 13 14 15 16 17 18 19 D
20 21
22 23
F Eint op[8][7] = { {0, 2, 6, 11, 15, 20, 22},//A操作 {1, 3, 8, 12, 17, 21, 23},//B操作 {10, 9, 8, 7, 6, 5, 4},//C操作 {19, 18, 17, 16, 15, 14, 13},//D操作 {23, 21, 17, 12, 8, 3, 1},//E操作 {22, 20, 15, 11, 6, 2, 0},//F操作 {13, 14, 15, 16, 17, 18, 19},//G操作 {4, 5, 6, 7, 8, 9, 10},//H操作 };
估价函数:由于每进行一次操作中间8个数都有一个数出去,另一个数进来,
所以找出中间八个数中出现最多的数记为cnt,所以估价函数为8 - cnt;int f() { static int sum[4];//防止每次调用估价函数都要重复开空间节省时间复杂度与空间复杂度 memset(sum, 0, sizeof sum); int cnt = 0; for (int i = 0; i < 8; i ++ ) sum[q[center[i]]] ++ ;//记录8个中间的数出现的次数 for (int i = 1; i <= 3; i ++ ) cnt = max(cnt, sum[i]);//找出8个中间的数出现的最多的数 return 8 - cnt; }
又因为可以每次枚举同一个操作导致某一个分支较长,则会造成某个分支较多
但答案却在很浅的层,所以可以用迭代加深的方法:
注意一个细节:当第一次枚举了A下一次就不能枚举它的反操作即F;
因此解题过程:
1:用如上图的标号表示#型图
2:用迭代加深计算每一次操作所需的最小步数
3:用股价函数排除一定找不到的情况
4:当某次枚举了某个操作,下一次就不能枚举它的反操作
5:从A -> H枚举操作保证答案字典序最小
#include <cstdio> #include <cstring> #include <iostream> using namespace std; int op[8][7] = { {0, 2, 6, 11, 15, 20, 22},//A操作 {1, 3, 8, 12, 17, 21, 23},//B操作 {10, 9, 8, 7, 6, 5, 4},//C操作 {19, 18, 17, 16, 15, 14, 13},//D操作 {23, 21, 17, 12, 8, 3, 1},//E操作 {22, 20, 15, 11, 6, 2, 0},//F操作 {13, 14, 15, 16, 17, 18, 19},//G操作 {4, 5, 6, 7, 8, 9, 10},//H操作 }; int q[25]; int path[100];//记录进行了那步操作 int opposite[8] = {5, 4, 7, 6, 1, 0, 3, 2}; //按顺序记录每种操作的反操作,例如A(0)反操作为F(5) int center[8] = {6, 7, 8, 11, 12, 15, 16, 17};//记录中间8个数 int f() { static int sum[4];//防止每次调用估价函数都要重复开空间节省时间复杂度与空间复杂度 memset(sum, 0, sizeof sum); int cnt = 0; for (int i = 0; i < 8; i ++ ) sum[q[center[i]]] ++ ;//记录8个中间的数出现的次数 for (int i = 1; i <= 3; i ++ ) cnt = max(cnt, sum[i]);//找出8个中间的数出现的最多的数 return 8 - cnt; } void operators(int a)//进行a操作 { int x = q[op[a][0]];//将队头元素取出 for (int i = 0; i + 1 < 7; i ++ ) q[op[a][i]] = q[op[a][i + 1]];//所有元素向队头方向移动一个距离 q[op[a][6]] = x;//将队头赋给最后一个元素 } bool dfs(int depth, int max_depth, int last) { if (depth + f() > max_depth) return false; if (f() == 0) return true;//说明中间八个数字相同 for (int i = 0; i < 8; i ++ )//枚举8个操作 if (opposite[i] != last)//若上一次操作的反操作与当前操作相同则跳过 { operators(i);//进行i操作 path[depth] = i;//path记录当前depth是从哪个操作过来的 if (dfs(depth + 1, max_depth, i)) return true;//递归到下一层 operators(opposite[i]);//恢复现场 } return false;//若没有找到符合条件的则返回flase } int main() { while (scanf("%d", &q[0]), q[0]) { for (int i = 1; i <= 23; i ++ ) scanf("%d", &q[i]); int depth = 1; while (!dfs(0, depth, -1)) depth ++ ;//迭代加深 if (depth == 1) printf("No moves needed"); else for (int i = 0; i < depth; i ++ ) printf("%c", path[i] + 'A'); printf("\n%d\n", q[6]); } return 0; }