通过深度优先搜索枚举每一步的方块移动,结合模拟消除过程并使用多种剪枝策略来寻找答案。
OJ: roj
题目 ID: 3622
难度:提高+/省选-
标签:搜索模拟剪枝
创建: 2026-10-10 09:23
更新: 2026-10-10 09:23
形式化题目
非常重要:本题的实际数据内容为经典的《Mayan 游戏》(NOIP2011 提高组 D1T3),而前台题目描述被错误地替换成了《寻找道路》(NOIP2014 提高组 D2T2)。所有代码实现和本题解均以真实数据(Mayan 游戏)为准。
游戏在一个 -1。
输出格式要求:如果存在方案,输出
正解
思路
这是一个典型的带有复杂状态转移的搜索题,我们需要使用深度优先搜索(DFS)来枚举每一步操作,并配合充分的剪枝来通过。
在每一步枚举中,我们要模拟:
- 交换:将选中的方块向左或向右交换。
- 下落与消除:写一个模拟函数,循环执行“下落 - 消除”直到网格稳定不再发生任何消除。
为了保证能在时间限制内得出结果并输出字典序最小的答案,需要以下优化与剪枝:
- 字典序优先:枚举坐标时外层循环为
,内层循环为 (先选小的横坐标和小的纵坐标);对于同一个方块,优先尝试向右移动(即优先枚举 再枚举 )。 - 等效操作剪枝:如果一个方块向左移动,且左边的格子也有方块,那么这个动作等效于左边的方块向右移动。但后者在字典序中排在前面!因此,我们规定:当且仅当左边为空时,才允许方块向左移动。
- 同色交换剪枝:如果一个方块向右移动,且右边格子是同色方块,交换是毫无意义的,可以直接跳过。
- 不可消除剪枝:在任意时刻,统计网格中各种颜色方块的数量。如果存在某一种颜色的方块数量为
个或 个,它们永远无法凑齐 个来被消除,此时当前状态必然无解,可以直接 return false回溯。
代码
python
import sys
def fall(g):
for x in range(5):
ptr = 0
for y in range(7):
if g[x][y] != 0:
val = g[x][y]
g[x][y] = 0
g[x][ptr] = val
ptr += 1
def clear_blocks(g):
to_clear = [[False]*7 for _ in range(5)]
any_clear = False
for x in range(5):
for y in range(7):
c = g[x][y]
if c != 0:
if x + 2 < 5 and g[x+1][y] == c and g[x+2][y] == c:
to_clear[x][y] = to_clear[x+1][y] = to_clear[x+2][y] = True
any_clear = True
if y + 2 < 7 and g[x][y+1] == c and g[x][y+2] == c:
to_clear[x][y] = to_clear[x][y+1] = to_clear[x][y+2] = True
any_clear = True
if not any_clear:
return False
for x in range(5):
for y in range(7):
if to_clear[x][y]:
g[x][y] = 0
return True
def process(g):
fall(g)
while clear_blocks(g):
fall(g)
def solve():
sys.setrecursionlimit(200000)
input_data = sys.stdin.read().split()
if not input_data:
return
n = int(input_data[0])
g = [[0]*7 for _ in range(5)]
idx = 1
for x in range(5):
y = 0
while idx < len(input_data):
val = int(input_data[idx])
idx += 1
if val == 0:
break
g[x][y] = val
y += 1
ans = []
def dfs(step, current_g):
if step == n:
for x in range(5):
if current_g[x][0] != 0:
return False
return True
cnt = [0] * 11
for x in range(5):
for y in range(7):
if current_g[x][y] != 0:
cnt[current_g[x][y]] += 1
for i in range(1, 11):
if cnt[i] == 1 or cnt[i] == 2:
return False
for x in range(5):
for y in range(7):
if current_g[x][y] == 0:
continue
# Move right
if x + 1 < 5 and current_g[x][y] != current_g[x+1][y]:
next_g = [col[:] for col in current_g]
next_g[x][y], next_g[x+1][y] = next_g[x+1][y], next_g[x][y]
process(next_g)
ans.append((x, y, 1))
if dfs(step + 1, next_g):
return True
ans.pop()
# Move left
if x - 1 >= 0 and current_g[x-1][y] == 0:
next_g = [col[:] for col in current_g]
next_g[x][y], next_g[x-1][y] = next_g[x-1][y], next_g[x][y]
process(next_g)
ans.append((x, y, -1))
if dfs(step + 1, next_g):
return True
ans.pop()
return False
if dfs(0, g):
for move in ans:
print(f"{move[0]} {move[1]} {move[2]}")
else:
print("-1")
if __name__ == '__main__':
solve()cpp
#include <iostream>
#include <vector>
#include <cstring>
using namespace std;
int n;
int initial_grid[5][7];
struct Move {
int x, y, g;
} ans[10];
// 方块下落逻辑:让每一列悬空的方块落到底部
void fall(int g[5][7]) {
for (int x = 0; x < 5; ++x) {
int ptr = 0;
for (int y = 0; y < 7; ++y) {
if (g[x][y] != 0) {
g[x][ptr++] = g[x][y];
}
}
for (int y = ptr; y < 7; ++y) {
g[x][y] = 0;
}
}
}
// 消除连续 >= 3 的同色方块
bool clear_blocks(int g[5][7]) {
bool to_clear[5][7] = {false};
bool any_clear = false;
for (int x = 0; x < 5; ++x) {
for (int y = 0; y < 7; ++y) {
if (g[x][y] != 0) {
int val = g[x][y];
// 水平检测
if (x - 1 >= 0 && x + 1 < 5 && g[x-1][y] == val && g[x+1][y] == val) {
to_clear[x-1][y] = to_clear[x][y] = to_clear[x+1][y] = true;
any_clear = true;
}
// 垂直检测
if (y - 1 >= 0 && y + 1 < 7 && g[x][y-1] == val && g[x][y+1] == val) {
to_clear[x][y-1] = to_clear[x][y] = to_clear[x][y+1] = true;
any_clear = true;
}
}
}
}
if (!any_clear) return false;
for (int x = 0; x < 5; ++x) {
for (int y = 0; y < 7; ++y) {
if (to_clear[x][y]) {
g[x][y] = 0;
}
}
}
return true;
}
// 掉落与消除循环,直到稳定
void process(int g[5][7]) {
fall(g);
while (clear_blocks(g)) {
fall(g);
}
}
// 深度优先搜索
bool dfs(int step, int g[5][7]) {
if (step == n) {
// 检查是否全部消除完
for (int x = 0; x < 5; ++x) {
if (g[x][0] != 0) return false;
}
return true;
}
// 剪枝1:如果某种颜色的方块只剩 1 个或 2 个,绝对不可能消除,直接剪枝
int cnt[11] = {0};
for (int x = 0; x < 5; ++x) {
for (int y = 0; y < 7; ++y) {
if (g[x][y] != 0) {
cnt[g[x][y]]++;
}
}
}
for (int i = 1; i <= 10; ++i) {
if (cnt[i] == 1 || cnt[i] == 2) return false;
}
int next_g[5][7];
for (int x = 0; x < 5; ++x) {
for (int y = 0; y < 7; ++y) {
if (g[x][y] == 0) continue;
// 剪枝及移动优先级1:优先向右移动 (字典序要求 x 小、g=1 优先)
if (x + 1 < 5) {
if (g[x][y] != g[x+1][y]) { // 剪枝:颜色相同交换无意义
memcpy(next_g, g, sizeof(next_g));
swap(next_g[x][y], next_g[x+1][y]);
process(next_g);
ans[step] = {x, y, 1};
if (dfs(step + 1, next_g)) return true;
}
}
// 剪枝及移动优先级2:向左移动 (当且仅当左侧为空时才移动)
// 如果左侧有方块,将其向左移动等效于将其左侧的方块向右移动,而后者字典序更优!
if (x - 1 >= 0 && g[x-1][y] == 0) {
memcpy(next_g, g, sizeof(next_g));
swap(next_g[x][y], next_g[x-1][y]);
process(next_g);
ans[step] = {x, y, -1};
if (dfs(step + 1, next_g)) return true;
}
}
}
return false;
}
int main() {
ios_base::sync_with_stdio(false);
cin.tie(NULL);
if (!(cin >> n)) return 0;
memset(initial_grid, 0, sizeof(initial_grid));
for (int x = 0; x < 5; ++x) {
int y = 0;
int val;
while (cin >> val && val != 0) {
initial_grid[x][y++] = val;
}
}
if (dfs(0, initial_grid)) {
for (int i = 0; i < n; ++i) {
cout << ans[i].x << " " << ans[i].y << " " << ans[i].g << "\n";
}
} else {
cout << "-1\n";
}
return 0;
}复杂度
- 时间复杂度:最坏情况下要搜索
个可能的操作,深度为 。理论上的搜索树大小约为 。但由于大量剪枝(特别是等效操作剪枝和同色剪枝),实际搜索树的规模非常小,完全可以通过。单次状态转移需要模拟消除,花费常数级别的时间,总时间复杂度在剪枝后能稳定通过。 - 空间复杂度:由于 DFS 深度最大为
,每层需要一个 的二维数组记录当前状态。空间复杂度为 ,远小于内存限制。
总结
- 坑点一:题面与数据严重不符。 题面是《寻找道路》,但数据是《Mayan 游戏》。我们必须完全根据数据的真实意图进行解题。
- 坑点二:字典序的正确处理和等效操作的去除。 如果不加限制地向左和向右枚举,不仅会导致死循环/冗余搜索,还会破坏字典序的判定。左移只有在“目标位置为空”时才有独立意义。
- 坑点三:连锁消除的模拟。 方块被标记消除后必须统一清空,然后再执行掉落,掉落后还要再次检查是否有新的消除,必须使用
while循环直至没有方块被标记为止。

