[NOIP2011] 玛雅游戏(回溯)

题目描述

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 所示的局面。


输入

输入文件mayan.in,共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。

样例输入

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

样例输出

2 1 1
3 1 1
3 0 1

提示


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




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





【数据范围】

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

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

膜拜大佬300行之毅力,我只打了60行。。。

算法分析:dfs

dfs:对7*5中每个非0的点按照x从小到大,y从小到大,先右后左进行枚举,注意右边界点不能向右,左边界点不能向左,枚举时调用move函数并记录相应操作;每次枚举前要把原来的矩阵copy一份,便于回溯;当超过n时,判断是否为空,如果为空就输出记录的操作。具体解释见代码。


#include<cstdio>
#include<iostream>
#include<cstring>
#include<cstdlib>
#define mem(a,b) memset(a,b,sizeof(a))
#define cop(a,b) memcpy(a,b,sizeof(b))
using namespace std;
int n,b[10][10],cnt,st[10],st2[10],st3[10];
bool ok[10][10];
bool wipe()//每次调用消除全图的所有可以消除的块 
{
	mem(ok,0);bool fu=0;//ok数组标记要消除的块,fu标记本次是否消除了块 
	//一定要标记不能直接删除,因为会有一个块为多次消除做贡献的情况 
	for(int i=1;i<=5;i++)
		for(int j=1;j<=7;j++)//我把矩阵开到了10*10,所以不用考虑越界,多考虑的一定不影响结果 
		{	if(b[i][j]&&b[i][j]==b[i][j+1]&&b[i][j]==b[i][j+2]) ok[i][j]=ok[i][j+1]=ok[i][j+2]=1;
			if(b[i][j]&&b[i][j]==b[i+1][j]&&b[i][j]==b[i+2][j]) ok[i][j]=ok[i+1][j]=ok[i+2][j]=1;
		}
	//每三个判断一次是否删除(注意判断的块要为真,都是0就不要消了) 
	for(int i=1;i<=5;i++) for(int j=1;j<=7;j++) if(ok[i][j]) b[i][j]=0,fu=1;
	//把标记的块删除 
	return fu;//为了方便后面判断 
}
void fall(){for(int i=1;i<=5;i++){int j=2;while(j<=7){while(j>1&&b[i][j]&&!b[i][j-1]) b[i][j-1]=b[i][j],b[i][j]=0,j--; j++;}}}
//将全地图的能降落的块降落,注意从下往上枚举(因为下面的块掉了会影响后面的块是否降落)
//还要注意细节(因为j>1写成了j>0调了一小时T.T) 
bool check(){for(int i=1;i<=5;i++) for(int j=1;j<=7;j++) if(b[i][j]!=0) return 0; return 1;}
//判断地图是否为空 
void print(){for(int i=1;i<=cnt;i++) printf("%d %d %d\n",st[i]-1,st2[i]-1,st3[i]);exit(0);}
//按步输出结果 并直接退出程序(exit(0)直接终止程序),省去回溯回去的过程 
void move(int x,int y,int ai,int bi){int tmp=b[ai][bi];b[ai][bi]=b[x][y];b[x][y]=tmp;}
//就是交换x,y和ai,bi的位置(或许名字起得不好) 
void mark(int x,int y,int z){st[cnt]=x;st2[cnt]=y;st3[cnt]=z;}//记录操作 
void dfs()
{
	if(cnt==n) if(check()) print();else return;//假如操作了n步,判断地图是否为空,为空就输出结果 
	for(int i=1;i<=5;i++)
		for(int j=1;j<=7;j++)
		{
			if(!b[i][j]) break;//如果为空那么后面都为空,没必要再搜索了 
			int tmp[10][10];//备份矩阵 
			cop(tmp,b);//把b赋给tmp 
			if(i<5)//优先右移,i==5是不能右移 
			{	cnt++;move(i,j,i+1,j);mark(i,j,1);
				while(1){fall();if(!wipe()) break;}//能消除一直消除,一定要先降落 
				dfs();cnt--;cop(b,tmp);//回溯回来返回之前状态 
			}
			if(i>1&&!b[i-1][j])//剪枝,如果左边不为0的话一定会被左边向右搜回来,所以只有当左边为0时才有必要搜索 
			{	cnt++;move(i,j,i-1,j);mark(i,j,-1);
				while(1){fall();if(!wipe()) break;}
				dfs();cnt--;cop(b,tmp);//返回之前状态 
			}
		}
}
int main()
{
	//freopen("mayan.in","r",stdin);
	//freopen("mayan.out","w",stdout);
	scanf("%d",&n);
	for(int i=1;i<=5;i++)//读入矩阵,注意不要读反,刚开始没看样例读错了...而且题目默认是从0开始的矩阵,所以我输出时都减了1 
		for(int j=1;;j++)
		{	scanf("%d",&b[i][j]);
			if(!b[i][j]) break;
		}
	dfs();
	printf("-1");//如果到这里还没有结束程序那么一定无解,输出-1就好 
	return 0;
}




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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值