题解:洛谷 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\) 的字符串,其中只包含字符 O 和 X,该行中的第 \(i\) 个字符表示第 \(i\) 个孔在此状态下是被覆盖还是未覆盖,第一行和最后一行必须全部都是 O。
【输入样例】
3
【输出样例】
OOO
OXO
OXX
OOX
XOX
XXX
XXO
XOO
OOO
【核心思想】
-
问题分析:给定 \(n\) 个洞(每个洞有盖/未盖两种状态),要求构造一个长度为 \(2^n + 1\) 的状态序列,使得:
- 首态和末态均为全
O(全空) - 相邻状态之间恰好翻转一个洞(盖或揭开)
- 每个可能的状态(共 \(2^n\) 个)恰好出现一次(除首末全
O重复外)
本质是一个格雷码构造问题——在 \(n\) 维超立方体上寻找一条哈密顿回路,相邻顶点仅有一位不同。
- 首态和末态均为全
-
算法选择:
- DFS回溯搜索:将每个状态视为 \(n\) 位二进制数,从全 \(0\) 出发,每次翻转一位,用访问标记避免重复,深度优先搜索直到遍历所有 \(2^n\) 个状态后回到全 \(0\)
-
关键步骤:
- 初始化:
- 输出首态全
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内部当满足条件时统一输出所有记录的状态
- 初始化:
-
时间/空间复杂度:
- 时间复杂度:\(O(n \cdot 2^n)\),每个状态有 \(n\) 种翻转选择,共 \(2^n\) 个状态
- 空间复杂度:\(O(2^n \cdot n)\),存储所有状态序列和访问标记
-
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
浙公网安备 33010602011771号