题解:洛谷 P6183 The Rock Game

【题目来源】

洛谷:P6183 [USACO10MAR] The Rock Game S - 洛谷

【题目描述】

在奶牛回家休息和娱乐之前,Farmer John 希望它们通过玩游戏获得一些智力上的刺激。

游戏板由 \(n\) 个相同的洞组成,这些洞最初都是空的。一头母牛要么用石头盖住一个空的洞,要么揭开一个先前被盖住的洞。游戏状态的定义是所有洞是否被石头覆盖的情况。

游戏的目标是让奶牛到达每个可能的游戏状态一次,最后回到初始状态。

以下是他们其中一次游戏的示例(空的洞用 O 表示,用石头盖住的洞用 X 表示):

时刻 洞 1 洞 2 洞 3 描述
\(0\) O O O 一开始所有的洞都是空的
\(1\) O O X 盖上洞 3
\(2\) X O X 盖上洞 1
\(3\) X O O 打开洞 3
\(4\) X X O 盖上洞 2
\(5\) O X O 打开洞 1
\(6\) O X X 盖上洞 3
\(7\) X X X 盖上洞 1

现在牛被卡住玩不下去了!他们必须打开一个洞,然而不管他们打开哪个洞,他们都会到达一个他们已经到达过的状态。例如,如果他们从第二个洞中取出岩石,他们将到达他们在时刻 \(2\) 已经访问过的状态(X O X)。

下面是一个 3 个孔的有效解决方案:

时间 洞 1 洞 2 洞 3 描述
\(0\) O O O 一开始所有的洞都是空的
\(1\) O X O 盖上洞 2
\(2\) O X X 盖上洞 3
\(3\) O O X 打开洞 2
\(4\) X O X 盖上洞 1
\(5\) X X X 盖上洞 2
\(6\) X X O 打开洞 3
\(7\) X O O 打开洞 2
\(8\) O O O 打开洞 1,恢复到原来的状态

现在,奶牛们厌倦了这个游戏,它们想找你帮忙。

给定 \(n\),求游戏的有效解决方案序列。如果有多个解决方案,则输出任意一个

【输入】

仅一行,一个整数 \(n\)

【输出】

\(2^n+1\) 行,每行一个长度为 \(n\) 的字符串,其中只包含字符 OX,该行中的第 \(i\) 个字符表示第 \(i\) 个孔在此状态下是被覆盖还是未覆盖,第一行和最后一行必须全部都是 O

【输入样例】

3

【输出样例】

OOO
OXO
OXX
OOX
XOX
XXX
XXO
XOO
OOO

【核心思想】

  1. 问题分析:给定 \(n\) 个洞(每个洞有盖/未盖两种状态),要求构造一个长度为 \(2^n + 1\) 的状态序列,使得:

    • 首态和末态均为全 O(全空)
    • 相邻状态之间恰好翻转一个洞(盖或揭开)
    • 每个可能的状态(共 \(2^n\) 个)恰好出现一次(除首末全 O 重复外)

    本质是一个格雷码构造问题——在 \(n\) 维超立方体上寻找一条哈密顿回路,相邻顶点仅有一位不同。

  2. 算法选择

    • DFS回溯搜索:将每个状态视为 \(n\) 位二进制数,从全 \(0\) 出发,每次翻转一位,用访问标记避免重复,深度优先搜索直到遍历所有 \(2^n\) 个状态后回到全 \(0\)
  3. 关键步骤

    • 初始化
      • 输出首态全 O
      • 标记全 \(0\) 状态已访问(vis[0] = 1
      • 数组 \(a[1..n]\) 记录当前各洞状态(\(0\)O\(1\)X
    • DFS递归(参数 \(\text{step}\) 表示当前已生成状态数):
      • 终止条件\(\text{step} = 2^n\),输出所有记录的状态并退出程序
      • 逐位翻转尝试:对于每个位置 \(i \in [1, n]\)
        • 翻转 \(a[i]\)(盖或揭开)
        • 计算当前状态的十进制值 \(\text{calc}() = \sum_{j=1}^{n} a[j] \cdot 2^{n-j}\)
        • 剪枝:若该状态已访问(vis[calc()] == 1),则还原 \(a[i]\) 并跳过
        • 记录与标记:标记该状态已访问,将当前 \(a[1..n]\) 存入 \(\text{ans}[\text{step}][1..n]\)
        • 递归\(\text{DFS}(\text{step} + 1)\)
        • 回溯:取消访问标记,还原 \(a[i]\)
    • 状态输出:DFS内部当满足条件时统一输出所有记录的状态
  4. 时间/空间复杂度

    • 时间复杂度:\(O(n \cdot 2^n)\),每个状态有 \(n\) 种翻转选择,共 \(2^n\) 个状态
    • 空间复杂度:\(O(2^n \cdot n)\),存储所有状态序列和访问标记
  5. DFS构造格雷码的核心思想

    • 超立方体哈密顿路径\(n\) 个二进制位对应 \(n\) 维超立方体的 \(2^n\) 个顶点,每次翻转一位即沿超立方体的一条边移动,目标是经过每个顶点恰好一次并回到起点
    • 回溯与剪枝:通过 vis 数组记录已访问状态,避免重复访问,DFS自然探索所有可能路径
    • 状态编码:将 \(n\) 位二进制状态压缩为十进制整数,实现 \(O(1)\) 的访问标记查询
    • 解的存在性保证\(n\) 维超立方体是哈密顿图,对于 \(n \geq 1\) 总存在这样的回路,DFS一定能找到解
    • 适用于状态空间遍历、格雷码生成、超立方体路径构造类问题

【解题思路】

【算法标签】

普及- #DFS-一维

【代码详解】

#include <bits/stdc++.h>
using namespace std;
int n, a[20], vis[65536];
int ans[65536][20];
int calc()  // 将二进制数转为十进制数
{
    int ans=0;
    for (int i=1; i<=n; i++) {
        ans = ans*2+a[i];
    }
    return ans;
}
void dfs(int step) {
    if (step==pow(2, n)) {  // 当step等于2^n,就进行输出
        for (int i=1; i<=pow(2, n); i++) {  // 遍历ans二维数组
            for (int j=1; j<=n; j++) {
                if (ans[i][j]==1) cout << "X";  // 如果当前位置为1就输出X
                else cout << "O";  // 否则就输出O
            }
            cout << endl;  // 每输出完后就换一行
        }
        exit(0);  // 只要完成一组输出,就结束程序
    }
    for (int i=1; i<=n; i++) {  // 遍历1到n个位置
        a[i] = !a[i];  // 对当前位置进行取反操作
        if (vis[calc()]==1) {  // 如果当前二进制数访问过
            a[i] = !a[i];  // 还原现场
            continue;  // 继续下一次循环
        }
        vis[calc()] = 1;  // 如果这个数之前没有出现过,则标记为1
        for (int j=1; j<=n; j++) {  // 使用二维数组记录下当前二进制数
            ans[step][j] = a[j];
        }
        dfs(step+1);  // 进行下一次搜索
        vis[calc()] = 0;  // 还原现场
        a[i] = !a[i];

    }
}
int main()
{
    cin >> n;  // 输入n
    for (int i=1; i<=n; i++) {  // 先输出第一行全O
        cout << 'O';
    }
    cout << endl;
    vis[0] = 1;  // 标记全0的二进制数已经访问过
    dfs(1);  // 进行dfs深搜
    return 0;
}

【运行结果】

3
OOO
XOO
XXO
OXO
OXX
XXX
XOX
OOX
OOO
posted @ 2026-08-29 09:39  团爸讲算法  阅读(5)  评论(0)    收藏  举报