[NOIP2011 提高组复赛] Mayan游戏题解

题目描述

Mayan puzzle 是最近流行起来的一个游戏。游戏界面是一个7 行 ×5 列的棋盘,上面堆放着一些方块,方块不能悬空堆放,即方块必须放在最下面一行,或者放在其他方块之上。游戏通关是指在规定的步数内消除所有的方块,消除方块的规则如下:

  1. 每步移动可以且仅可以沿横向(即向左或向右)拖动某一方块一格:当拖动这一方块时,如果拖动后到达的位置(以下称目标位置)也有方块,那么这两个方块将交换位置(参见输入输出样例说明中的图 6 到图 7);如果目标位置上没有方块,那么被拖动的方块将从原来的竖列中抽出,并从目标位置上掉落(直到不悬空,参见下面图 1 和图 2);

  1. 任一时刻,如果在一横行或者竖列上有连续三个或者三个以上相同颜色的方块,则它们将立即被消除(参见图1 到图3)。

注意:

a) 如果同时有多组方块满足消除条件,几组方块会同时被消除(例如下面图 4,三个颜色为 1 的方块和三个颜色为 2 的方块会同时被消除,最后剩下一个颜色为 2 的方块)。

b) 当出现行和列都满足消除条件且行列共享某个方块时,行和列上满足消除条件的所有方块会被同时消除(例如下面图5 所示的情形,5 个方块会同时被消除)。

  1. 方块消除之后,消除位置之上的方块将掉落,掉落后可能会引起新的方块消除。注意:掉落的过程中将不会有方块的消除。

上面图 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

输入输出样例

输入 #1

3
1 0
2 1 0
2 3 4 0
3 1 0
2 4 3 4 0

输出 #1

2 1 1
3 1 1
3 0 1

说明/提示

【输入输出样例说明】

按箭头方向的顺序分别为图 6 到图 11

样例输入的游戏局面如上面第一个图片所示,依次移动的三步是:(2,1) 处的方格向右移动,(3,1) 处的方格向右移动,(3,0) 处的方格向右移动,最后可以将棋盘上所有方块消除。

【数据范围】

对于 30% 的数据,初始棋盘上的方块都在棋盘的最下面一行;

对于 100% 的数据,0<n≤5。

题目分析

首先,这题一看肯定是个暴力搜索,因为题目中的变换/操作略复杂,所以我们尽量分开写函数解决每块小操作

注:本文中的地图变量 [ i ][ j ] 均代表自下而上第i行,自左而右第j列

首先把图用二维数组表示出来,结构体,图方便

struct node{int pad[10][10];}

题目中总共满足三个操作:

1.掉落

当一个方块下方没有方块时,会往下掉,直到到底或下方有方块

实现[x][y]方块的掉落

void drop(int x,int y,node &u){   //同步修改掉,用地址,下同
	int r=x;                 //用来记录当前掉落块,接下来有用
	if(u.pad[x][y]==0)return; //如果是个“空块”,不做了
	bool flag=0;              //用来判定这个块有没有掉落
	while(u.pad[x-1][y]==0&&x>0)//下方为空且不是最底层
	{
		flag=1;
		swap(u.pad[x-1][y],u.pad[x][y]);//掉落即交换
		x--;                //自己的位置降低
	}
	if(flag)drop(r+1,y,u);      //如果自己掉了,自己上方也掉
}

2.横向拖动/交换

每个方块可以向左/右拖动,即与左/右交换方块,于是我们可以用简单的swap搞定

void move(int x,int y,int d,node &u){
	swap(u.pad[x][y],u.pad[x][y+d]);//d是方向即1或-1,直接拿来用
	drop(x+1,y,u);drop(x,y+d,u);   //换来的可能是 0 ,换出去的一定不是零,会在宽搜时候保证
        //我的上方方块可能会掉落,我过去的地方可能会使我掉落
}

3.消除

横向或纵向的三个连续相同块可以消掉,且可以共用块

因为有了共用块的性质,所以肯定不能无脑找到就消除,不妨打标记

void del(node &u){
	bool need=0;
	memset(vis,0,sizeof(vis));   //vis数组用来打标记
	for(int i=0;i<7;i++)        //从左下往右上搜
	for(int j=0;j<5;j++)        //只要考虑向右和向上的连续块即可
		if(u.pad[i][j]!=0){ //存在方块
	   		if(u.pad[i+1][j]==u.pad[i][j]&&u.pad[i+2][j]==u.pad[i][j])//纵向三个相连
		   		vis[i][j]=vis[i+1][j]=vis[i+2][j]=1;
	       		if(u.pad[i][j+1]==u.pad[i][j]&&u.pad[i][j+2]==u.pad[i][j])//横向三个相连
		   		vis[i][j]=vis[i][j+1]=vis[i][j+2]=1;
	}
	
	for(int i=6;i>=0;i--)   //消除的时候从上往下,可以同步drop操作
	for(int j=0;j<5;j++)
	   if(vis[i][j]){       //被标记了要消除
		u.pad[i][j]=0;  //消掉
		drop(i+1,j,u);  //上方的方块下落
		need=1;
	}
	if(need)del(u);
}

所以我们用一个need来判定有没有消除,如果有消除,那么再扫一遍,看在掉落后有没有新的可以消除的块

--------------------------------------------------------------------- 华丽丽的分割线

所有的操作解决,要消除完毕才算完成

一个check解决

bool check(node u){
	for(int i=0;i<7;i++)
	for(int j=0;j<5;j++)
	    if(u.pad[i][j]!=0)return false;//还有方块,失败
	return true;
}

AC代码

#include<bits/stdc++.h>
using namespace std;
int n,a,cnt[5];
bool vis[8][8],flag;
struct node{
	short pad[8][8];
	bool operator<(const node &a)const
    	{
      		for(int i(0);i<7;++i)
        	for(int j(0);j<5;++j){
                	if(pad[i][j]!=a.pad[i][j])return pad[i][j]<a.pad[i][j];
                }
        	return false;
    	}
}s;

struct point{
	node ground;
	short step,mx[6],my[6];
	int dir[6];
};
queue<point>line;

bool check(node u){
	for(int i=0;i<7;i++)
	for(int j=0;j<5;j++)
	    if(u.pad[i][j]!=0)return 0;
	return 1;
}

void drop(int x,int y,node &u){
	int r=x;
	if(u.pad[x][y]==0)return;
	bool flag=1;
	while(u.pad[x-1][y]==0&&x>0)
	{
		flag=1;
		u.pad[x-1][y]=u.pad[x][y];
		u.pad[x][y]=0;
		x--;
	}
	if(flag)drop(r+1,y,u);
}

void del(node &u){
	bool need=0;
	memset(vis,0,sizeof(vis));
	for(int i=0;i<7;i++)
	for(int j=0;j<5;j++)
		if(u.pad[i][j]!=0){
			if(u.pad[i+1][j]==u.pad[i][j]&&u.pad[i+2][j]==u.pad[i][j])
				vis[i][j]=vis[i+1][j]=vis[i+2][j]=1;
			if(u.pad[i][j+1]==u.pad[i][j]&&u.pad[i][j+2]==u.pad[i][j])
				vis[i][j]=vis[i][j+1]=vis[i][j+2]=1;
		}
	
	for(int i=6;i>=0;i--)
	for(int j=0;j<5;j++)
		if(vis[i][j]){
			u.pad[i][j]=0;
			drop(i+1,j,u);
			need=1;
		}
	
	if(need)del(u);
}

void move(int x,int y,int d,node &u){
	swap(u.pad[x][y],u.pad[x][y+d]);
	drop(x+1,y,u);drop(x,y+d,u);
}

void bfs(){
	map<node,bool>used;
	used[s]=1;
	point e;
	e.ground=s;
	e.step=0;
	line.push(e);
	while(!line.empty())
	{
		point u=line.front(),v;
        line.pop();
		node back=u.ground;
		for(int j=0;j<5;j++)
		for(int i=0;i<7;i++)
		{
			if(back.pad[i][j]!=0)
			{
				if(j+1<5){
					v=u;v.step++;
					v.mx[v.step]=j,v.my[v.step]=i;
	                v.dir[v.step]=1;
					move(i,j,1,v.ground);
					del(v.ground);
					if(v.step<n&&!used[v.ground]){line.push(v);used[v.ground]=1;}
					if(v.step==n&&check(v.ground)){
						for(int k=1;k<=n;k++)
							cout<<v.mx[k]<<" "<<v.my[k]<<" "<<v.dir[k]<<endl;
						flag=1;
						return ;
					}
				}
				if(j-1>=0&&back.pad[i][j-1]==0){
					v=u;v.step++;
					v.mx[v.step]=j,v.my[v.step]=i;
					v.dir[v.step]=-1;
					move(i,j,-1,v.ground);
					del(v.ground);
					if(v.step<n&&!used[v.ground]){line.push(v);used[v.ground]=1;}
					if(v.step==n&&check(v.ground)){
						for(int k=1;k<=n;k++)
							cout<<v.mx[k]<<" "<<v.my[k]<<" "<<v.dir[k]<<endl;
						flag=1;
						return ;
					}	
				}
			}
		}
	}
	return ;
}

int main(){
	cin>>n;
	for(int i=0;i<=4;i++)
	{
		while(cin>>a)
		{
			if(!a)break;
			s.pad[cnt[i]++][i]=a;
		}
	}
	bfs();
	if(!flag)printf("-1");
	return 0;
} 

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值