[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;
}




發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章