「一本通 1.4 练习 1」棋盘游戏

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

把 4×4 棋盘编码成 16 位掩码,相邻异色交换即两位同时翻转,在有限状态上跑 BFS 求最少步数。

OJ: roj

题目 ID: 1451

难度:普及

标签:BFS状态压缩

创建: 2026-09-30 11:24

更新: 2026-10-05 23:56

题目描述

4×4 棋盘上有 8 个黑棋(1)和 8 个白棋(0),每次可交换一对有公共边的相邻棋子。给出初始局面和目标局面(各为 4 行 01 串,中间空一行),求最少移动步数。

输入共 8 行 01 串,前 4 行为初始棋盘,后 4 行为目标棋盘,每行 4 个 0/1 数字;输出一行整数表示最少步数。

思路

局面总数为 C(16,8)=12870。把棋盘编码成 16 位整数,相邻异色交换等价于两位同时翻转,在状态图上跑 BFS 即可。

参考代码

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
# rainboy的学习导航网站: https://idx.roj.ac.cn
# create_at: 2026-07-05 21:47
# update_at: 2026-07-05 21:47

import sys
from collections import deque

# 一对相邻格子编码为两个 16 位掩码:状态与这两格异或即完成一次交换
ADJ = (
    [1 << (r * 4 + c) | 1 << (r * 4 + c + 1) for r in range(4) for c in range(3)]  # 横向 12 对
    + [1 << (r * 4 + c) | 1 << ((r + 1) * 4 + c) for r in range(3) for c in range(4)]  # 纵向 12 对
)


def board_of(row0: int, row1: int, row2: int, row3: int) -> int:
    """四行 01 数字各占 4 位、先读到的行占高位,拼成一个 16 位掩码。"""
    return row0 << 12 | row1 << 8 | row2 << 4 | row3


def solve() -> None:
    # 每行是 4 位 01 串(前导 0 有意义),按二进制解析;空行被 split 吞掉
    lines = sys.stdin.buffer.read().split()
    data = iter(int(line, 2) for line in lines)
    start = board_of(next(data), next(data), next(data), next(data))
    target = board_of(next(data), next(data), next(data), next(data))

    dist = {start: 0}  # start 出发的最短步数
    q = deque([start])
    while q and target not in dist:
        u = q.popleft()
        for pair in ADJ:
            if u & pair in (0, pair):
                continue             # 两格同色,交换后局面不变
            v = u ^ pair             # 两格异色,交换 = 两位置同时翻转
            if v not in dist:
                dist[v] = dist[u] + 1
                q.append(v)

    print(dist[target])


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:56
 * update_at: 2026-10-05 23:56
 */
#include <cstdio>
#include <cstring>
#include <iostream>
#include <queue>
#include <string>
using namespace std;

typedef long long ll;

int dist[1 << 16]; // 每个 16 位掩码状态到起点的最短步数,-1 表示未访问
int pair_mask[24]; // 24 对相邻格子的掩码
int pm_cnt;        // 相邻对数量

// 读取一个 4 位 01 串,cin >> s 会自动跳过空行和空白字符
int read_row() {
    string s;
    cin >> s;
    return stoi(s, nullptr, 2);
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    // 预生成 24 对相邻格子的掩码:横向 12 对 + 纵向 12 对
    pm_cnt = 0;
    for (int r = 0; r < 4; ++r) {
        for (int c = 0; c < 3; ++c) {
            pair_mask[pm_cnt++] = (1 << (r * 4 + c)) | (1 << (r * 4 + c + 1));
        }
    }
    for (int r = 0; r < 3; ++r) {
        for (int c = 0; c < 4; ++c) {
            pair_mask[pm_cnt++] = (1 << (r * 4 + c)) | (1 << ((r + 1) * 4 + c));
        }
    }

    // 读入 8 个 4 位 01 串,前四个拼成初始状态,后四个拼成目标状态
    int start = 0, target = 0;
    for (int i = 0; i < 4; ++i) {
        int row = read_row();
        start = (start << 4) | row;
    }
    for (int i = 0; i < 4; ++i) {
        int row = read_row();
        target = (target << 4) | row;
    }

    // BFS:每条边代价为 1,第一次到达目标即为最少步数
    memset(dist, -1, sizeof(dist));
    queue<int> q;
    dist[start] = 0;
    q.push(start);

    while (!q.empty()) {
        int u = q.front();
        q.pop();
        if (u == target) break;
        int d = dist[u];
        for (int i = 0; i < pm_cnt; ++i) {
            int p = pair_mask[i];
            int sub = u & p;
            if (sub == 0 || sub == p) continue; // 同色,交换不改变局面
            int v = u ^ p;                       // 异色,交换等价于两格同时翻转
            if (dist[v] == -1) {
                dist[v] = d + 1;
                q.push(v);
            }
        }
    }

    cout << dist[target] << "\n";
    return 0;
}