1 条题解
-
0
问题描述
本题是一个基于“Gap”纸牌游戏的搜索问题。我们需要通过一系列合法的移动操作,将初始的纸牌布局转换为目标布局。目标布局要求每一行的纸牌按照花色递增的顺序排列。我们需要找到最少的移动次数,或者判断无法达到目标布局。
输入输出格式
- 输入:
- 第一行是一个整数
a
,表示初始布局的数量。 - 接下来是
a
组数据,每组数据由五行组成:- 第一行是空行。
- 接下来的四行,每行包含七个两位数,表示初始布局的纸牌。
- 第一行是一个整数
- 输出:
- 对于每个初始布局,输出一行,表示达到目标布局所需的最少移动次数。如果无法达到目标布局,输出
-1
。
- 对于每个初始布局,输出一行,表示达到目标布局所需的最少移动次数。如果无法达到目标布局,输出
解题思路
-
数据结构设计:
- 使用一个二维数组
c[10][10]
来存储纸牌的布局,其中c[i][0]
表示第i
行的空位。 - 使用一个结构体
wolf
来表示当前的纸牌布局状态,方便在搜索过程中存储和比较状态。
- 使用一个二维数组
-
预处理:
- 将数值为
1
的纸牌移动到每行的最左边,形成初始的“Gap”布局。 - 检查是否已经满足目标布局,如果是,则直接输出
0
。
- 将数值为
-
搜索算法:
- 使用广度优先搜索(BFS)来寻找从初始布局到目标布局的最短路径。
- 使用队列
Q
存储当前状态和对应的移动次数。 - 使用集合
S
存储已经访问过的状态,避免重复搜索。
-
状态转移:
- 对于每个空位,尝试用其左边邻居的后继纸牌填充该空位。
- 如果后继纸牌存在且合法,则生成新的状态并加入队列。
-
目标检测:
- 检查当前状态是否满足目标布局,即每一行的纸牌是否按照花色递增的顺序排列。
- 如果满足目标布局,输出当前的移动次数并结束搜索。
-
特殊情况处理:
- 如果队列为空且仍未找到目标布局,则输出
-1
。
- 如果队列为空且仍未找到目标布局,则输出
代码解析
#include <iostream> #include <queue> #include <set> #include <algorithm> using namespace std; // 定义一个结构体表示当前的纸牌布局 struct wolf { int c[4][8]; // 4 行,每行 8 列(包括空位) }; // 重载结构体的比较运算符,用于 set 中的去重 inline bool operator<(const wolf &a, const wolf &b) { for (int i = 0; i < 4; i++) { for (int j = 0; j < 8; j++) { if (a.c[i][j] != b.c[i][j]) { return a.c[i][j] < b.c[i][j]; // 按字典序比较 } } } return false; } int main() { int a; cin >> a; // 读取初始布局的数量 while (a--) { wolf initial; // 定义初始状态 // 读取初始布局 for (int i = 0; i < 4; i++) { for (int j = 1; j < 8; j++) { cin >> initial.c[i][j]; // 跳过空位,从第 2 列开始读取纸牌 } } // 初始化空位 for (int i = 0; i < 4; i++) { initial.c[i][0] = 11 + i * 10; // 每行的空位编号为 11, 21, 31, 41 } // 移除数值为 1 的纸牌,放置到每行的最左边 for (int i = 0; i < 4; i++) { for (int j = 1; j < 8; j++) { if (initial.c[i][j] % 10 == 1) { // 如果纸牌的数值为 1 initial.c[i][j] = 0; // 将该位置设为空 } } } // 初始化队列和集合 queue<pair<wolf, int> > Q; // 队列用于 BFS,存储当前状态和移动次数 set<wolf> S; // 集合用于存储已经访问过的状态,避免重复搜索 Q.push(make_pair(initial, 0)); // 将初始状态和移动次数 0 加入队列 S.insert(initial); // 将初始状态加入集合 bool found = false; // 标记是否找到目标布局 while (!Q.empty()) { wolf current = Q.front().first; // 当前状态 int cost = Q.front().second; // 当前移动次数 Q.pop(); // 检查是否达到目标布局 bool is_goal = true; for (int i = 0; i < 4; i++) { for (int j = 0; j < 7; j++) { if (current.c[i][j] != 11 + i * 10 + j) { // 检查是否满足目标布局 is_goal = false; break; } } if (!is_goal) break; } if (is_goal) { found = true; cout << cost << endl; // 输出移动次数 break; } // 尝试所有可能的移动 for (int i = 0; i < 4; i++) { for (int j = 1; j < 8; j++) { if (!current.c[i][j] && current.c[i][j - 1] && current.c[i][j - 1] % 10 != 7) { // 如果当前空位的左边有纸牌且该纸牌的数值不是 7 int row = -1, col = -1; // 找到后继纸牌的位置 for (int k = 0; k < 4; k++) { for (int l = 0; l < 8; l++) { if (current.c[k][l] == current.c[i][j - 1] + 1) { row = k; col = l; break; } } if (row != -1) break; } if (row != -1) { // 生成新的状态 wolf next_state = current; next_state.c[row][col] = 0; // 将后继纸牌移出原位置 next_state.c[i][j] = current.c[i][j - 1] + 1; // 将后继纸牌移动到当前空位 // 检查是否已经访问过 if (S.find(next_state) == S.end()) { S.insert(next_state); // 将新状态加入集合 Q.push(make_pair(next_state, cost + 1)); // 将新状态加入队列,移动次数加 1 } } } } } } if (!found) { cout << "-1" << endl; // 如果未找到目标布局,输出 -1 } } return 0; }
代码逻辑分析
-
输入处理:
- 读取初始布局,并将数值为
1
的纸牌移动到每行的最左边,形成初始的“Gap”布局。
- 读取初始布局,并将数值为
-
BFS 搜索:
- 使用队列存储当前状态和对应的移动次数。
- 使用集合存储已经访问过的状态,避免重复搜索。
- 对于每个状态,尝试所有可能的移动操作,生成新的状态并加入队列。
-
目标检测:
- 检查当前状态是否满足目标布局,即每一行的纸牌是否按照花色递增的顺序排列。
- 如果满足目标布局,输出当前的移动次数并结束搜索。
-
特殊情况处理:
- 如果队列为空且仍未找到目标布局,则输出
-1
。
- 如果队列为空且仍未找到目标布局,则输出
时间复杂度分析
- 每个状态最多有
4 * 7 = 28
种可能的移动操作。 - 使用集合
S
去重,避免重复搜索。 - 最坏情况下,状态空间的大小为
8!
(每行有 8 个位置,每行的纸牌排列方式),因此时间复杂度为O(8! * 28)
。
总结
本题是一个典型的搜索问题,通过广度优先搜索(BFS)可以找到从初始布局到目标布局的最短路径。关键在于设计合适的数据结构来存储状态,并通过集合去重来避免重复搜索。
- 输入:
- 1
信息
- ID
- 1047
- 时间
- 1000ms
- 内存
- 256MiB
- 难度
- 10
- 标签
- 递交数
- 3
- 已通过
- 1
- 上传者