【题目链接】
CodeVS 1136 Mayan游戏 COGS 622 玛雅游戏
【题目描述】
Mayan puzzle 是最近流行起来的一个游戏。游戏界面是一个7 行5 列的棋盘,上面堆放着一些方块,方块不能悬空堆放,即方块必须放在最下面一行,或者放在其他方块之上。游戏通关是指在规定的步数内消除所有的方块,消除方块的规则如下:
1、 每步移动可以且仅可以沿横向(即向左或向右)拖动某一方块一格:当拖动这一方块时,如果拖动后到达的位置(以下称目标位置)也有方块,那么这两个方块将交换位置(参见输入输出样例说明中的图6 到图7);如果目标位置上没有方块,那么被拖动的方块将从原来的竖列中抽出,并从目标位置上掉落(直到不悬空,参见下面图1 和图2); 2、 任一时刻,如果在一横行或者竖列上有连续三个或者三个以上相同颜色的方块,则它们将立即被消除(参见图1到图3)。
注意: a) 如果同时有多组方块满足消除条件,几组方块会同时被消除(例如下面图4,三个颜色为1 的方块和三个颜色为2 的方块会同时被消除,最后剩下一个颜色为2 的方块)。 b) 当出现行和列都满足消除条件且行列共享某个方块时,行和列上满足消除条件的所有方块会被同时消除(例如下面图5 所示的情形,5 个方块会同时被消除)。 3、 方块消除之后,消除位置之上的方块将掉落,掉落后可能会引起新的方块消除。注意:掉落的过程中将不会有方块的消除。 上面图1 到图3 给出了在棋盘上移动一块方块之后棋盘的变化。棋盘的左下角方块的坐标为(0, 0),将位于(3, 3)的方块向左移动之后,游戏界面从图1 变成图2 所示的状态,此时在一竖列上有连续三块颜色为4 的方块,满足消除条件,消除连续3 块颜色为4 的方块后,上方的颜色为3 的方块掉落,形成图3 所示的局面。
输入格式:
共6 行。 第一行为一个正整数 n,表示要求游戏通关的步数。 接下来的 5 行,描述7*5 的游戏界面。每行若干个整数,每两个整数之间用一个空格隔开,每行以一个0 结束,自下向上表示每竖列方块的颜色编号(颜色不多于10 种,从1 开始顺序编号,相同数字表示相同颜色)。 输入数据保证初始棋盘中没有可以消除的方块。
输出格式:
如果有解决方案,输出n 行,每行包含3 个整数x,y,g,表示一次移动,每两个整数之间用一个空格隔开,其中(x,y)表示要移动的方块的坐标,g 表示移动的方向,1 表示向右移动,-1 表示向左移动。注意:多组解时,按照x 为第一关健字,y 为第二关健字,1优先于-1,给出一组字典序最小的解。游戏界面左下角的坐标为(0,0)。 如果没有解决方案,输出一行,包含一个整数-1。
【解题思路】
据传这是搜索的一个极麻烦的例题,今晚终得一试,但没有想象中那么难。 此题的难点在细节 主算法框架选择回溯深搜。
直接搜可能超时,考虑一些优化, 由于我们的操作是交换方块,假设a在b的左边,那么右移a与左移b是等价的,所以我们统一方向,每个块只尝试右移。这个策略的优化效果很不错,状态数至少要减半。 另一个优化是针对无解情况的,因为在无解的情况下,我们要搜遍整个状态空间才敢断定该状态无解,考虑当前局面下每种颜色块的个数,如果有某种颜色的个数小于3,我们就可以判断该情况无解了,因为无论怎样移动都不能消除这几个块,这个剪枝的优化效果一般。
值得注意的一些细节~~(坑点)~~:
- 消除与下落的具体实现
- 消除时为了处理行列交叉消除的情况,先逐行扫描,标记出在行上要消除的块,再逐列扫描,标记出在列上要消除的块,最后一并消除。
- 下落与消除要交替进行,直到不可消除
- 棋盘坐标与我们通常使用的二维数组下标的不同
- 由上一条导致的字典序问题
- 如果我们只考虑右移,那么当我们遇到空格时,操作不是空格右移,而是空格右的块(如果有的话)左移
好像也没有那么多的样子,还是那句话,注意细节,仔细想好,引用一位神犇的名言:
Think twice,code once;
【AC代码】
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <climits>
#define MAXSTEP 10
struct Operation{
int x, y;
int dir;
Operation() {}
Operation(int x, int y, int dir) : x(x), y(y), dir(dir) {}
};
const int m = 7, n = 5;
int G[m][n];
int num[11];
int N;
Operation ans[MAXSTEP];
inline bool isFinal(){
for(int i = 0; i < m; i++) for(int j = 0; j < n; j++) if(G[i][j]) return false;
return true;
}
inline bool clean(){
bool f[m][n] = { false };
for(int i = 0; i < m; i++) for(int j = 0; j < n - 2; j++) if(G[i][j]){
int cur = G[i][j];
int l = j, r = j + 1;
while(cur == G[i][r]) r++;
if(r - l >= 3) for(int k = l; k < r; k++) f[i][k] = true;
j = r - 1;
}
for(int j = 0; j < n; j++) for(int i = 0; i < m - 2; i++) if(G[i][j]){
int cur = G[i][j];
int l = i, r = i + 1;
while(cur == G[r][j]) r++;
if(r - l >= 3) for(int k = l; k < r; k++) f[k][j] = true;
i = r - 1;
}
bool flag = false;
for(int i = 0; i < m; i++) for(int j = 0; j < n; j++) if(f[i][j]){
flag = true;
num[ G[i][j] ]--;
G[i][j] = 0;
}
return flag;
}
inline void fall(){
for(int i = 1; i < m; i++) for(int j = 0; j < n; j++) if(G[i][j]){
for(int x = i; !G[x - 1][j] && x > 0; x--) std::swap(G[x][j], G[x - 1][j]);
}
}
inline void pull(int d, int i, int j){
ans[d] = (G[i][j] ? Operation(i, j, 1) : Operation(i, j + 1, -1));
std::swap(G[i][j], G[i][j + 1]);
}
inline int minNum(){
int ans = INT_MAX;
for(int i = 0; i < 11; i++) if(num[i]) ans = std::min(ans, num[i]);
return ans;
}
bool dfs(int d){
if(d == N) return isFinal();
else if(minNum() < 3) return false;
else for(int j = 0; j < n - 1; j++) for(int i = 0; i < m; i++) if(G[i][j] || G[i][j + 1]){
int oldG[m][n], oldNum[11];
memcpy(oldG, G, sizeof G);
memcpy(oldNum, num, sizeof num);
pull(d, i, j);
do fall();
while(clean());
if(dfs(d + 1)) return true;
memcpy(num, oldNum, sizeof num);
memcpy(G, oldG, sizeof G);
}
return false;
}
inline void solve(){
if(dfs(0)) for(int i = 0; i < N; i++) printf("%d %d %d\n", ans[i].y, ans[i].x, ans[i].dir);
else printf("-1");
}
int main(){
// freopen("mayan.in", "r", stdin), freopen("mayan.out", "w", stdout);
scanf("%d", &N);
for(int j = 0; j < n; j++){
int x;
for(int i = 0; scanf("%d", &x) == 1 && x; i++) G[i][j] = x, num[x]++;
}
solve();
// fclose(stdin), fclose(stdout);
return 0;
}