「一本通 1.4 例 2」魔板

GitHub跳转原题关系图返回列表

状态只有 8! 个,把每个排列看作节点、A/B/C 三种操作看作边,从基本状态 12345678 BFS,邻居按 A→B→C 顺序扩展且只在首达时记前驱,回溯即得字典序最小的最短操作序列。

OJ: roj

题目 ID: 1449

难度:普及-

标签:BFS状态压缩置换

创建: 2026-09-30 11:11

更新: 2026-10-05 23:47

题目描述

给定 1∼81\sim 8 的一个排列作为魔板状态(从左上角起顺时针读出)。三种操作:A 交换上下两行;B 最右列插入最左列;C 中央 2×22\times2 顺时针旋转。

输入一行 8 个整数表示目标状态;输出最短操作序列的长度,以及字典序最小的最短操作串(除最后一行外每行 60 个字符)。样例:输入 2 6 8 4 5 7 3 1,输出 7 / BCABCCB。

思路

状态总数只有 8!=403208!=40320,把每个排列看作节点、三种操作看作边,从基本状态 12345678 做 BFS;邻居固定按 A、B、C 顺序扩展且只在首次入队时记前驱,首次到达目标的路径就是字典序最小的最短操作序列,回溯前驱输出即可。

参考代码

Python 版:

python
#!/usr/bin/env python3
# Author by Rainboy blog: https://rainboylv.com github: https://github.com/rainboylvx
# rbook: -> https://rbook.roj.ac.cn  https://rbook2.roj.ac.cn
# 魔板 Magic Squares 题解
# create_at: 2026-07-05 22:00
# update_at: 2026-07-05 22:00

import sys
from collections import deque

# 三种操作在顺时针序列上的下标重排:s' = tuple(s[i] for i in perm)
PERMS = [
    (7, 6, 5, 4, 3, 2, 1, 0),  # A:交换上下两行,序列整体翻转
    (3, 0, 1, 2, 5, 6, 7, 4),  # B:最右列插入最左列
    (0, 6, 1, 3, 4, 2, 5, 7),  # C:中央 2x2 顺时针旋转
]

START = tuple(range(1, 9))  # 基本状态 1..8


def solve() -> None:
    target = tuple(map(int, sys.stdin.read().split()))  # 目标状态,顺时针序列

    # BFS:按 A、B、C 顺序扩展,首次到达即为字典序最小的最短操作序列
    prev: dict[tuple[int, ...], tuple[tuple[int, ...], str]] = {}
    queue = deque([START])
    while queue:
        state = queue.popleft()
        if state == target:
            break
        for letter, perm in zip("ABC", PERMS):
            nxt = tuple(state[i] for i in perm)
            if nxt not in prev:
                prev[nxt] = (state, letter)
                queue.append(nxt)

    # 回溯还原路径
    path: list[str] = []
    state = target
    while state != START:
        state, letter = prev[state]
        path.append(letter)
    answer = ''.join(reversed(path))

    print(len(answer))
    # 除最后一行外每行 60 个字符
    print('\n'.join(answer[i:i + 60] for i in range(0, len(answer), 60)))


if __name__ == "__main__":
    solve()

C++ 版(同一算法):

cpp
/**
 * Author by Rainboy blog: https://rainboylv.com github: https://github.com/rainboylvx
 * rbook: -> https://rbook.roj.ac.cn  https://rbook2.roj.ac.cn
 * rainboy的学习导航网站: https://idx.roj.ac.cn
 * create_at: 2026-10-05 23:47
 * update_at: 2026-10-05 23:47
 */
// main.cpp:BFS 求魔板从基本状态到目标状态的最短字典序操作序列。
#include <iostream>
#include <map>
#include <queue>
#include <string>

typedef long long ll;

// 三种操作在顺时针序列上的下标重排:新序列第 i 位来自旧序列 OPS[op][i] 位
const int OPS[3][8] = {
    {7, 6, 5, 4, 3, 2, 1, 0}, // A:交换上下两行,序列整体翻转
    {3, 0, 1, 2, 5, 6, 7, 4}, // B:最右列插入最左列
    {0, 6, 1, 3, 4, 2, 5, 7}  // C:中央 2x2 顺时针旋转
};
const int POW9[8] = {1, 9, 81, 729, 6561, 59049, 531441, 4782969}; // 9 进制各位权值

std::map<int, int> prev_state; // prev_state[状态] = 前驱状态
std::map<int, char> prev_op;   // prev_op[状态] = 到达该状态所用的操作字母
std::queue<int> bfs_queue;     // BFS 队列,存编码后的状态

// 把 8 位排列编码成 9 进制整数;每位是 1~8,不含 0,故编码唯一
int encode(int s[8]) {
    int code = 0;
    for (int i = 0; i < 8; i++) {
        code += s[i] * POW9[i];
    }
    return code;
}

// 把 9 进制编码还原成 8 位排列
void decode(int code, int s[8]) {
    for (int i = 0; i < 8; i++) {
        s[i] = code % 9;
        code /= 9;
    }
}

int main() {
    int target[8];
    for (int i = 0; i < 8; i++) {
        std::cin >> target[i];
    }
    int start[8] = {1, 2, 3, 4, 5, 6, 7, 8}; // 基本状态
    int start_code = encode(start);
    int target_code = encode(target);

    // BFS:邻居固定按 A、B、C 顺序扩展,且只在首次入队时记前驱,
    // 因此首次到达目标时的路径就是字典序最小的最短操作序列
    prev_state[start_code] = -1;
    bfs_queue.push(start_code);
    while (!bfs_queue.empty()) {
        int cur = bfs_queue.front();
        bfs_queue.pop();
        if (cur == target_code) {
            break;
        }
        int s[8];
        decode(cur, s);
        for (int op = 0; op < 3; op++) {
            int nxt[8];
            for (int i = 0; i < 8; i++) {
                nxt[i] = s[OPS[op][i]];
            }
            int nxt_code = encode(nxt);
            if (prev_state.find(nxt_code) == prev_state.end()) {
                prev_state[nxt_code] = cur;
                prev_op[nxt_code] = 'A' + op;
                bfs_queue.push(nxt_code);
            }
        }
    }

    // 从目标沿前驱回溯到基本状态,得到的是逆序序列
    std::string answer;
    int cur = target_code;
    while (cur != start_code) {
        answer += prev_op[cur];
        cur = prev_state[cur];
    }
    ll len = answer.size();
    for (ll i = 0; i < len / 2; i++) {
        char tmp = answer[i];
        answer[i] = answer[len - 1 - i];
        answer[len - 1 - i] = tmp;
    }

    std::cout << len << "\n";
    // 除最后一行外每行输出 60 个字符
    for (ll i = 0; i < len; i += 60) {
        std::cout << answer.substr(i, 60) << "\n";
    }
    return 0;
}