acwing-181. 回转游戏

考点

  • IDAstar

题解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
/*
0 1
2 3
4 5 6 7 8 9 10
11 12
13 14 15 16 17 18 19
20 21
22 23
*/
#include <bits/stdc++.h>
using namespace std;
const int maxn = 2e2 + 50;

// 打表操作序列
int op[maxn][maxn] = {
{0, 2, 6, 11, 15, 20, 22}, {1, 3, 8, 12, 17, 21, 23},
{10, 9, 8, 7, 6, 5, 4}, {19, 18, 17, 16, 15, 14, 13},
{23, 21, 17, 12, 8, 3, 1}, {22, 20, 15, 11, 6, 2, 0},
{13, 14, 15, 16, 17, 18, 19}, {4, 5, 6, 7, 8, 9, 10}};
// 相反的操作序号
int opposite[maxn] = {5, 4, 7, 6, 1, 0, 3, 2};
// 中心八个编号
int center[maxn] = {6, 7, 8, 11, 12, 15, 16, 17};
int q[maxn], path[maxn];

int f() {
static int sum[4];
memset(sum, 0, sizeof sum);
for (int i = 0; i < 8; i++) sum[q[center[i]]]++;
int s = 0;
for (int i = 1; i <= 3; i++) s = max(s, sum[i]);
return 8 - s;
}

void operation(int x) {
int t = q[op[x][0]];
for (int i = 0; i < 6; i++) q[op[x][i]] = q[op[x][i + 1]];
q[op[x][6]] = t;
}

bool dfs(int depth, int max_depth, int last) {
// 可行性剪枝:初始状态到目前状态的开销 + 未来最小开销都大于阈值,剪枝
if (depth + f() > max_depth) return false;
// 到达目标状态,返回
if (!f()) return true;
for (int i = 0; i < 8; i++) {
// 最优性剪枝:当前操作是上一次操作的反向,剪枝
if (opposite[i] == last) continue;
operation(i);
path[depth] = i;
if (dfs(depth + 1, max_depth, i)) return true;
operation(opposite[i]);
}
return false;
}

int main() {
while (cin >> q[0] && q[0]) {
for (int i = 1; i < 24; ++i) cin >> q[i];
int depth = 0;
while (!dfs(0, depth, -1)) ++depth;
if (!depth)
cout << "No moves needed";
else
for (int i = 0; i < depth; ++i) cout << (char)('A' + path[i]);
cout << endl << q[6] << endl;
}
return 0;
}

思路

八个方向枚举,只能用DFS,要找最少的操作次数,只能是迭代加深,又没什么剪枝性质,只能选择IDAstar

考虑估价函数:

  • 统计中间8个方格中,出现次数最多的数的次数,记为k
  • 每次操作会从中间8个方格中移出1个数,再移入1个数,所以最多会减少一个不同的数
  • 所以估价函数设为8 - k

由于每次操作实际上就是平移数组,可以先打表操作序列以降低编程难度;

还有最优性剪枝,如果当前操作是上一次操作的反向,剪枝;

题目要求字典序最小,按照ABCDEFGH的顺序枚举即可。