题目背景
- 在成功地发明了魔方之后,鲁比克先生发明了它的二维版本,称作魔板。这是一张有8个大小相同的格子的魔板:
|1|2|3|4|
|8|7|6|5|
题目描述
- 我们知道魔板的每一个方格都有一种颜色。这8种颜色用前8个正整数来表示。可以用颜色的序列来表示一种魔板状态,规定从魔板的左上角开始,沿顺时针方向依次取出整数,构成一个颜色序列。对于上图的魔板状态,我们用序列(1,2,3,4,5,6,7,8)来表示。这是基本状态。
- 这里提供三种基本操作,分别用大写字母“A”,“B”,“C”来表示(可以通过这些操作改变魔板的状态):
- “A”:交换上下两行;
- “B”:将最右边的一列插入最左边;
- “C”:魔板中央四格作顺时针旋转。
- 下面是对基本状态进行操作的示范:
- A:
|8|7|6|5|
|1|2|3|4|
- B:
|4|1|2|3|
|5|8|7|6|
- C:
|1|7|2|4|
|8|6|3|5|
- 对于每种可能的状态,这三种基本操作都可以使用。
- 你要编程计算用最少的基本操作完成基本状态到目标状态的转换,输出基本操作序列。
输入输出格式
输入格式
- 只有一行,包括8个整数,用空格分开(这些整数在范围 1——8 之间)不换行,表示目标状态。
输出格式
- Line 1: 包括一个整数,表示最短操作序列的长度。
- Line 2: 在字典序中最早出现的操作序列,用字符串表示,除最后一行外,每行输出60个字符。
输入输出样例
输入样例#1
输出样例#1
说明
- 题目翻译来自NOCOW。
USACO Training Section 3.2
原题地址
分析 哈希表 + 广度优先搜索
- 看到这样的题目应该很容易想到搜索,但如果用深度优先搜索做,则可能会一直无法变换到目标状态,从而导致死循环,所以我们只能用广度优先搜索,那么最先搜到的步数肯定最少。而问题的关键在于如何对已经搜索过的状态进行判重,于是对于这一类的问题,我们很容易想到哈希表。
- 考虑一种最为简单的思路,把一种状态中的八个数字对应转换成八位十进制数上的某一位上,但这样是存储不下的。我们想到除余法,发现共有全排列
8!=40320
个情况。于是我们设的模数大概是比
40320
稍大的素数,然后将每一种情况对应到哈希表中。(膜拜用康托展开做的大神)
- 鉴于本题的特殊性,其实我们还可以考虑另一种较为简便的哈希函数,因为状态只包含这八个数字,我们统一令其减一,则我们就可以将这八个数字作为一个八位八进制数上的每一位,也就是基数转换法。
- 这样做的优点在哪呢?我们会发现最大的状态
(76543210)8=(16434824)10
,那么我们只要开一个大约
1650
万的
bool
数组就可以直接用来判重了,而不需要再使用除余法等等的操作。并且每一种状态是唯一的,也不可能发生冲突的情况,另外转成八进制数的乘以
8
<script type="math/tex" id="MathJax-Element-478">8</script>也可以用位运算中的右移来快速实现。
代码
#include <iostream>
#include <cstdio>
using namespace std;
const int N = 5e4 + 5, M = 165e5;
const int G[3][9] = {{0, 8, 7, 6, 5, 4, 3, 2, 1},
{0, 4, 1, 2, 3, 6, 7, 8, 5},
{0, 1, 7, 2, 4, 5, 3, 6, 8}};
int h[N][9], a[9], pf[N][2]; char stk[N];
int t, w = 1, x, edt, now, top;
bool vis[M];
int main()
{
for (int i = 1; i <= 8; ++i)
scanf("%d", &x), edt = (edt << 3) + x - 1;
for (int i = 1; i <= 8; ++i)
now = (now << 3) + (h[1][i] = i - 1);
vis[now] = true;
if (now == edt) return puts("0"), 0;
while ((t++) < w)
{
for (int i = 0; i < 3; ++i)
{
now = 0;
for (int j = 1; j <= 8; ++j)
now = (now << 3) + (a[j] = h[t][G[i][j]]);
if (vis[now]) continue;
vis[now] = true;
pf[++w][0] = t; pf[w][1] = i + 'A';
h[w][0] = h[t][0] + 1;
for (int j = 1; j <= 8; ++j) h[w][j] = a[j];
if (now == edt)
{
printf("%d\n", h[w][0]); x = w;
while (pf[x][0]) stk[++top] = pf[x][1], x = pf[x][0];
for (int i = top; i >= 1; --i) putchar(stk[i]);
return 0;
}
}
}
return 0;
}