[NOIP 2011 提高组] Mayan 游戏

GitHub跳转原题关系图返回列表

按字典序 DFS 枚举至多 5 步移动,完整模拟重力、同时消除与连锁反应。

OJ: luogu

题目 ID: P1312

难度:省选/NOI-

标签:DFS模拟剪枝python

日期: 2026-07-16 20:10

题意

在 5 列 7 行消除棋盘上执行恰好给定步数的横移,输出清空棋盘的字典序最小方案。

思路

搜索按 xy、方向 1-1 的顺序枚举,第一次找到的解自然字典序最小。交换后反复执行:列内压紧、标记所有横向或纵向三连、同时清零,直到不再消除。

向左与非空块交换,等价于较小 x 方块向右交换,前面已枚举过;因此左移只保留目标为空的情况。相同颜色交换不改变状态,也跳过。

若某种剩余颜色数量只有 1 或 2,它永远不能被三连消掉,可立即失败;相同 (步数, 棋盘) 的失败状态只搜索一次。

Python 知识

  • 棋盘用 5 个长度 7 的列表表示,[column[:] for ...] 清楚复制分支状态。
  • Counter 一行统计各颜色剩余数量。
  • set.update 适合汇总所有必须同时消除的坐标。
  • map(lambda move: " ".join(map(str, move)), path) 格式化三元操作序列。

代码

python
import sys
from collections import Counter


data = iter(map(int, sys.stdin.buffer.read().split()))
move_limit = next(data)
board = [[0] * 7 for _ in range(5)]
for column in range(5):
    row = 0
    while (color := next(data)):
        board[column][row] = color
        row += 1


def settle(state):
    while True:
        for column in range(5):
            values = [color for color in state[column] if color]
            state[column][:] = values + [0] * (7 - len(values))
        marked = set()
        for x in range(5):
            for y in range(7):
                color = state[x][y]
                if not color:
                    continue
                if x + 2 < 5 and state[x + 1][y] == state[x + 2][y] == color:
                    marked.update(((x, y), (x + 1, y), (x + 2, y)))
                if y + 2 < 7 and state[x][y + 1] == state[x][y + 2] == color:
                    marked.update(((x, y), (x, y + 1), (x, y + 2)))
        if not marked:
            return
        for x, y in marked:
            state[x][y] = 0


def encode(state):
    return tuple(value for column in state for value in column)


def is_empty(state):
    return all(not color for column in state for color in column)


def viable(state):
    return all(amount >= 3 for amount in Counter(
        color for column in state for color in column if color
    ).values())


path = []
failed = set()


def dfs(step, state):
    if step == move_limit:
        return is_empty(state)
    if is_empty(state) or not viable(state):
        return False
    key = step, encode(state)
    if key in failed:
        return False
    for x in range(5):
        for y in range(7):
            if not state[x][y]:
                continue
            for direction in (1, -1):
                target = x + direction
                if not 0 <= target < 5:
                    continue
                if direction == 1 and state[x][y] == state[target][y]:
                    continue
                if direction == -1 and state[target][y] != 0:
                    continue
                next_state = [column[:] for column in state]
                next_state[x][y], next_state[target][y] = next_state[target][y], next_state[x][y]
                settle(next_state)
                path.append((x, y, direction))
                if dfs(step + 1, next_state):
                    return True
                path.pop()
    failed.add(key)
    return False


if dfs(0, board):
    print("\n".join(map(lambda move: " ".join(map(str, move)), path)))
else:
    print(-1)

复杂度

搜索深度最多 5,最坏指数级;每次模拟只处理固定 35 格棋盘。

总结

本题的难点是模拟语义必须准确:先同时标记,再统一消除,重力后继续连锁,不能边扫描边删。