洛谷题单指南-进阶搜索-P1312 [NOIP 2011 提高组] Mayan 游戏

原题链接:https://www.luogu.com.cn/problem/P1312

题意解读:7*5方格棋盘,方块数字1~4,连在一起超过3个同样数字可以消除,每次移动一个方块,可以左移、右移,如果移动的地方有其他方块则交换,如果移动的地方没有其他方块则加到该列最后一个空位置,按照这样的顺序移动:从左往右、从下往上、先往左后往右,问能否在n步之内将所有方块消除,并输出移动了哪些方块和具体方向,1表示向右,-1表示向左。

解题思路:

由于本题限制了最大移动次数,因此可以采用搜索来解决。

一、搜索顺序

按列枚举所有方块,再针对一列的每一个方块,先向右移动、后向左移动,不断递归执行即可。

注意,向右移动时有两种情况:

1、右边有方块,需要将两者交换

2、右边没有方可,需要将当前位置方块移除,并插入到右边一列的末尾

向左移动只需要处理一种情况:

1、左边没有方块,将当前位置方块移除,并插入到左边一列的末尾

向左移动不需要处理交换的情况,因为在向右移动时已经重复了。

二、数据结构选型

棋盘显然是一个二维数据结构,要实现方块的移动和方块的消除,可以使用二维数组,但使用vector更容易操作,因此我们采用

vector<int> g[5]来表示棋盘,5个vector<int>表示5列,行在vector<int>内部表示。
三、算法实现
1、构建棋盘
处理输入即可
for(int i = 0; i <= 5; i++)
{
    int x;
    while(cin >> x && x != 0)
        g[i].push_back(x);
}

2、判断终态

当棋盘所有方块都消除时,vector的长度都是0,因此检查棋盘是否全部消除逻辑为

bool check(vector<int> g[5])
{
    bool ok = true;
    for(int i = 0; i < 5; i++)
    {
        if(g[i].size())
        {
            ok = false;
            break;
        }
    }
    return ok;
}

3、棋盘消除

要将连续长度超过3个同样的方块消除,需要分两步走

第一步,枚举所有方块,找出已所有方块为起点的连续3个(横向、纵向)相同方块,并用set记录,因为set可以去重

第二步,对已标记的所有方块进行消除操作,消除操作可以借助vector的erase函数解决

由于一次消除后,还可能引起连锁消除,因此消除之后要返回本次是否有成功消除的标记,可以根据该返回标记决定是否循环消除。

bool del()
{
    set<pair<int,int>, greater<pair<int, int>>> tags; //标记要被删除的方块,set可以去重
    for(int i = 0; i < 5; i++)
    {
        for(int j = 0; j < (int)g[i].size(); j++)
        {
            //横向
            if(i < 3 && j < g[i + 1].size() && j < g[i + 2].size() && g[i][j] && g[i][j] == g[i + 1][j] && g[i][j] == g[i + 2][j])
                tags.insert({i, j}), tags.insert({i + 1, j}), tags.insert({i + 2, j});
            //纵向
            if(j < (int)g[i].size() - 2 && g[i][j] && g[i][j] == g[i][j + 1] && g[i][j] == g[i][j + 2])
                tags.insert({i, j}), tags.insert({i, j + 1}), tags.insert({i, j + 2});
        }
    }
    bool deleted = tags.size() > 0;
    for(pair<int, int> pos : tags) //删除被标记的方块
    {
        g[pos.first].erase(g[pos.first].begin() + pos.second);
    }
    return deleted;
}

4、棋盘备份

由于在dfs过程中要保存棋盘以及恢复棋盘以回溯,需要复制操作

void copy(vector<int> dst[5], vector<int> src[5])
{
    for(int i = 0; i < 5; i++) dst[i] = src[i];
}

5、执行搜索

搜索过程就是第一步搜索顺序中介绍的流程,在搜索过程中需要记录三个参数

depth:当前搜索深度,用来记录已经移动了几步

maxdepth:最大搜索深度,用来判断是否已经到达限制的最大步数

vector<Op>:移动路径,用来记录每一步移动的方块左边和方向,Op是一个结构体

100分代码:

#include <bits/stdc++.h>
using namespace std;

int n;
vector<int> g[5];

struct Op
{
    int x, y, dir;
};

//备份src到dst
void copy(vector<int> dst[5], vector<int> src[5])
{
    for(int i = 0; i < 5; i++) dst[i] = src[i];
}

//检查棋盘方块是否全部被消除
bool check(vector<int> g[5])
{
    bool ok = true;
    for(int i = 0; i < 5; i++)
    {
        if(g[i].size())
        {
            ok = false;
            break;
        }
    }
    return ok;
}

//检查棋盘状态,将能消除的方块消除
bool del()
{
    set<pair<int,int>, greater<pair<int, int>>> tags; //标记要被删除的方块,set可以去重
    for(int i = 0; i < 5; i++)
    {
        for(int j = 0; j < (int)g[i].size(); j++)
        {
            //横向
            if(i < 3 && j < g[i + 1].size() && j < g[i + 2].size() && g[i][j] && g[i][j] == g[i + 1][j] && g[i][j] == g[i + 2][j])
                tags.insert({i, j}), tags.insert({i + 1, j}), tags.insert({i + 2, j});
            //纵向
            if(j < (int)g[i].size() - 2 && g[i][j] && g[i][j] == g[i][j + 1] && g[i][j] == g[i][j + 2])
                tags.insert({i, j}), tags.insert({i, j + 1}), tags.insert({i, j + 2});
        }
    }
    bool deleted = tags.size() > 0;
    for(pair<int, int> pos : tags) //删除被标记的方块
    {
        g[pos.first].erase(g[pos.first].begin() + pos.second);
    }
    return deleted;
}

//depth:当前递归深度 maxdepth:最大可递归深度 path:记录每次选择的移动方案
bool dfs(int depth, int maxdepth, vector<Op> path)
{
    if(depth == maxdepth) //递归深度达到目标深度
    {
        if(!check(g)) return false; //如果没有全部消除,则不再继续递归
        for(auto o : path) //输出移动路径
            cout << o.x << " " << o.y << " " << o.dir << endl;
        return true;
    }

    for(int i = 0; i < 5; i++)
    {
        for(int j = 0; j < g[i].size(); j++)
        {
            vector<int> t[5];
            copy(t, g); //备份棋盘
            if(i < 4 && j + 1 <= g[i + 1].size()) //右边不空,交换
            {
                swap(g[i][j], g[i + 1][j]); //交换方块
                while(del()); //检查并消除
                path.push_back({i, j, 1}); //记录动作
                if(dfs(depth + 1, maxdepth, path)) return true;
                path.pop_back(); //恢复
                copy(g, t); //恢复棋盘
            }
            else if(i < 4 && j + 1 > g[i + 1].size()) //右边是空,移动
            {
                g[i + 1].push_back(g[i][j]); //将g[i][j]移到右边
                g[i].erase(g[i].begin() + j); //将g[i][j]移除
                while(del()); //检查并消除
                path.push_back({i, j, 1}); //记录动作
                if(dfs(depth + 1, maxdepth, path)) return true;
                path.pop_back(); //恢复
                copy(g, t); //恢复棋盘
            }
            if(i > 0 && j + 1 > g[i - 1].size()) //左边是空,移动
            {   
                g[i - 1].push_back(g[i][j]); //将g[i][j]移到左边
                g[i].erase(g[i].begin() + j); //将g[i][j]移除
                while(del()); //检查并消除
                path.push_back({i, j, -1}); //记录动作
                if(dfs(depth + 1, maxdepth, path)) return true;
                path.pop_back(); //恢复
                copy(g, t); //恢复棋盘
            }
        }
    }

    return false;
}
int main()
{
    cin >> n;
    for(int i = 0; i <= 5; i++)
    {
        int x;
        while(cin >> x && x != 0)
            g[i].push_back(x);
    }
   
    vector<Op> path;
    if(!dfs(0, n, path)) cout << -1 << endl;
    
    return 0;
}

 

posted @   五月江城  阅读(10)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 【杂谈】分布式事务——高大上的无用知识?
点击右上角即可分享
微信分享提示