洛谷题单指南-进阶搜索-P1312 [NOIP 2011 提高组] Mayan 游戏
原题链接:https://www.luogu.com.cn/problem/P1312
题意解读:7*5方格棋盘,方块数字1~4,连在一起超过3个同样数字可以消除,每次移动一个方块,可以左移、右移,如果移动的地方有其他方块则交换,如果移动的地方没有其他方块则加到该列最后一个空位置,按照这样的顺序移动:从左往右、从下往上、先往左后往右,问能否在n步之内将所有方块消除,并输出移动了哪些方块和具体方向,1表示向右,-1表示向左。
解题思路:
由于本题限制了最大移动次数,因此可以采用搜索来解决。
一、搜索顺序
按列枚举所有方块,再针对一列的每一个方块,先向右移动、后向左移动,不断递归执行即可。
注意,向右移动时有两种情况:
1、右边有方块,需要将两者交换
2、右边没有方可,需要将当前位置方块移除,并插入到右边一列的末尾
向左移动只需要处理一种情况:
1、左边没有方块,将当前位置方块移除,并插入到左边一列的末尾
向左移动不需要处理交换的情况,因为在向右移动时已经重复了。
二、数据结构选型
棋盘显然是一个二维数据结构,要实现方块的移动和方块的消除,可以使用二维数组,但使用vector更容易操作,因此我们采用
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;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 【杂谈】分布式事务——高大上的无用知识?