UVa 1724 Solitaire
本文研究8×8棋盘上4个相同棋子的移动问题,允许普通移动和跳跃移动。给定初始和目标配置,判断能否在最多8步内转换。采用双向BFS算法优化搜索,将状态编码为排序的位置集合,通过生成所有可能移动来扩展搜索。算法利用哈希集合存储已访问状态,当两个搜索方向相遇时判定可达。复杂度分析表明双向BFS将状态空间从O(b^d)降至O(b^(d/2)),在合理时间内解决问题。关键技术包括状态编码、移动生成规则和双向
题目描述
在一个标准的 8 × 8 8 \times 8 8×8 棋盘上,有 4 4 4 个相同的棋子。每个移动可以执行以下两种操作之一:
- 普通移动:将棋子移动到相邻的空格(上、下、左、右)
- 跳跃移动:跳过相邻的一个棋子落到紧挨着的空格中
给定两个棋盘配置,每个配置由 8 8 8 个整数表示(分别描述 4 4 4 个棋子的行号和列号),判断是否能在最多 8 8 8 步内从第一个配置到达第二个配置。
解题思路
问题分析
这是一个典型的状态空间搜索问题,需要解决以下关键点:
1 1 1. 状态表示:由于棋子不可区分,我们需要用排序后的位置集合来表示状态
2 2 2. 状态空间:从 64 64 64 个位置中选 4 4 4 个,组合数为 C ( 64 , 4 ) ≈ 6.35 × 1 0 5 C(64,4) \approx 6.35 \times 10^5 C(64,4)≈6.35×105
3 3 3. 步数限制:最多 8 8 8 步,限制了搜索深度
4 4 4. 移动规则:需要正确处理普通移动和跳跃移动
算法选择
考虑到步数限制较小但状态空间较大,我们采用双向 BFS \texttt{BFS} BFS算法:
- 单向 BFS \texttt{BFS} BFS 复杂度: O ( b d ) O(b^d) O(bd),其中 b ≈ 32 b \approx 32 b≈32(分支因子), d = 8 d = 8 d=8 → 约 3 2 8 32^8 328 个状态
- 双向 BFS \texttt{BFS} BFS 复杂度: O ( b d / 2 ) O(b^{d/2}) O(bd/2) → 约 3 2 4 32^4 324 个状态,效率提升显著
关键技术细节
1. 状态编码
- 将 ( r , c ) (r, c) (r,c) 坐标编码为 0 − 63 0-63 0−63 的整数: p o s = ( r − 1 ) × 8 + ( c − 1 ) pos = (r-1) \times 8 + (c-1) pos=(r−1)×8+(c−1)
- 状态用排序后的 4 4 4 个整数向量表示,确保棋子不可区分性
2. 移动生成
对于每个棋子的四个方向:
- 普通移动:检查目标位置是否在棋盘内且为空
- 跳跃移动:检查中间位置有棋子且目标位置在棋盘内且为空
3. 双向 BFS \texttt{BFS} BFS 实现
- 从起点和终点同时开始搜索
- 使用两个队列和两个哈希集合
- 当两个搜索方向相遇时找到解
- 总步数控制: s t e p s 1 + s t e p s 2 ≤ 8 steps_1 + steps_2 \leq 8 steps1+steps2≤8
复杂度分析
- 时间复杂度: O ( b d / 2 ) = O ( 3 2 4 ) O(b^{d/2}) = O(32^4) O(bd/2)=O(324),在可接受范围内
- 空间复杂度: O ( b d / 2 ) O(b^{d/2}) O(bd/2),存储两个方向的已访问状态
优化技巧
- 哈希优化:使用
unordered_set
和自定义哈希函数 - IO \texttt{IO} IO 优化:使用
ios::sync_with_stdio(false)
加速输入输出 - 提前终止:发现相遇立即返回结果
参考代码
// Solitaire
// UVa ID: 1724
// Verdict: Accepted
// Submission Date: 2025-10-15
// UVa Run Time: 0.040s
//
// 版权所有(C)2025,邱秋。metaphysis # yeah dot net
#include <bits/stdc++.h>
using namespace std;
// 将行列位置编码为单个整数 (0-63)
int encode(int r, int c) { return (r - 1) * 8 + (c - 1); }
// 检查位置是否在棋盘范围内
bool inBoard(int r, int c) { return r >= 1 && r <= 8 && c >= 1 && c <= 8; }
// 状态类型:排序后的 4 个位置整数
typedef vector<int> State;
// 状态哈希函数
struct StateHash {
size_t operator()(const State& s) const {
size_t hash = 0;
for (int pos : s) {
hash = hash * 131 + pos;
}
return hash;
}
};
// 生成当前状态的所有下一步可能状态
vector<State> getNextStates(const State& current) {
vector<State> nextStates;
// 将当前状态转为集合便于快速查找
set<int> currentSet(current.begin(), current.end());
// 四个移动方向:下、上、右、左
const int dr[] = {1, -1, 0, 0};
const int dc[] = {0, 0, 1, -1};
// 对每个棋子尝试移动
for (int i = 0; i < 4; i++) {
int pos = current[i];
int r = pos / 8 + 1;
int c = pos % 8 + 1;
// 尝试四个方向
for (int dir = 0; dir < 4; dir++) {
// 尝试普通移动:移动到相邻空位
int nr = r + dr[dir];
int nc = c + dc[dir];
if (inBoard(nr, nc)) {
int newPos = encode(nr, nc);
if (currentSet.find(newPos) == currentSet.end()) {
State newState = current;
newState[i] = newPos;
sort(newState.begin(), newState.end());
nextStates.push_back(newState);
}
}
// 尝试跳跃移动:跳过相邻棋子
int mr = r + dr[dir];
int mc = c + dc[dir];
if (inBoard(mr, mc)) {
int midPos = encode(mr, mc);
if (currentSet.find(midPos) != currentSet.end()) {
int nr = r + 2 * dr[dir];
int nc = c + 2 * dc[dir];
if (inBoard(nr, nc)) {
int newPos = encode(nr, nc);
if (currentSet.find(newPos) == currentSet.end()) {
State newState = current;
newState[i] = newPos;
sort(newState.begin(), newState.end());
nextStates.push_back(newState);
}
}
}
}
}
}
return nextStates;
}
// 双向 BFS 检查是否能在 8 步内到达目标状态
bool canReach(const State& startState, const State& targetState) {
if (startState == targetState) return true;
// 使用两个队列和两个 visited 集合
queue<State> q1, q2;
unordered_set<State, StateHash> visited1, visited2;
q1.push(startState);
q2.push(targetState);
visited1.insert(startState);
visited2.insert(targetState);
int steps1 = 0, steps2 = 0;
while (!q1.empty() && !q2.empty()) {
// 从起点方向扩展一层
if (steps1 + steps2 < 8) {
int size1 = q1.size();
for (int i = 0; i < size1; i++) {
State current = q1.front();
q1.pop();
vector<State> nextStates = getNextStates(current);
for (const State& next : nextStates) {
if (visited1.find(next) != visited1.end()) continue;
// 检查是否在另一方向已经访问过
if (visited2.find(next) != visited2.end()) {
return true;
}
visited1.insert(next);
q1.push(next);
}
}
steps1++;
}
// 从终点方向扩展一层
if (steps1 + steps2 < 8) {
int size2 = q2.size();
for (int i = 0; i < size2; i++) {
State current = q2.front();
q2.pop();
vector<State> nextStates = getNextStates(current);
for (const State& next : nextStates) {
if (visited2.find(next) != visited2.end()) continue;
// 检查是否在另一方向已经访问过
if (visited1.find(next) != visited1.end()) {
return true;
}
visited2.insert(next);
q2.push(next);
}
}
steps2++;
}
// 如果总步数超过 8,停止搜索
if (steps1 + steps2 >= 8) break;
}
return false;
}
int main() {
cin.tie(0), cout.tie(0), ios::sync_with_stdio(false);
vector<int> start(8), target(8);
while (cin >> start[0]) {
for (int i = 1; i < 8; i++) cin >> start[i];
for (int i = 0; i < 8; i++) cin >> target[i];
State startState, targetState;
for (int i = 0; i < 4; i++) {
startState.push_back(encode(start[2 * i], start[2 * i + 1]));
targetState.push_back(encode(target[2 * i], target[2 * i + 1]));
}
sort(startState.begin(), startState.end());
sort(targetState.begin(), targetState.end());
if (canReach(startState, targetState)) {
cout << "YES" << endl;
} else {
cout << "NO" << endl;
}
}
return 0;
}
总结
本题通过**双向 BFS \texttt{BFS} BFS**算法高效解决了状态空间搜索问题,关键点在于:
- 合理的状态表示确保棋子不可区分
- 正确的移动规则实现
- 双向搜索大幅减少状态空间
- 严格的步数控制和优化技巧
这种解法在步数限制较小的情况下特别有效,是解决类似棋盘游戏问题的经典方法。
更多推荐
所有评论(0)