P1312 [NOIP 2011 提高组] Mayan 游戏

题目传送门

欢迎大家来我的博客喵

两个半小时大战暴力大法师,拼尽全力无法战胜。

参考文献


首先这个题数据这么小,肯定是爆搜。一般这种题都不是很好写,一定要注意细节,不要写错变量名,不要脑抽写错一些奇奇怪怪的地方,不然你都不知道挂哪了。

为了方便起见,我们写一些会用到的函数:

1. ept(p)

判断二维数组 \(p\) 是否全部清零。如果是则返回 1,否则返回 0,代码实现十分好写:

ept
inline bool ept(int p[N][N]){
	for(int i=0;i<5;i++){
		for(int j=0;j<7;j++){
			if(p[i][j]) return 0;
		}
	}
	return 1;
}

2. copy(p,q)

\(q\) 数组的值拷贝给 \(p\)。用来处理 dfs 回溯时的还原问题及下落时的重新赋值问题。

这个也很简单捏,直接 \(7 \times 5\) 枚举赋值即可。

copy
inline void copy(int p[N][N],int q[N][N]){
	for(int i=0;i<5;i++){
		for(int j=0;j<7;j++){
			p[i][j]=q[i][j];
		}
	}
}

3. drop(p)

用来处理 \(p\) 数组某些方块要下落的问题的,稍微复杂了一点。但总之也很简单,新开一个 \(c\) 数组,然后枚举每一排,将所有非 0 数字放在底部,最后 \(copy(p,c)\) 即可。

drop
inline void drop(int p[N][N]){
	int c[N][N];
	for(int i=0;i<5;i++){
		int k=-1;
		//k:指针,指向当前c数组存储到哪里了 
		for(int j=0;j<7;j++) c[i][j]=0;
		for(int j=0;j<7;j++){
			if(p[i][j]) c[i][++k]=p[i][j];
			//非0就让它"下落"
		}
	}
	//不要忘了最后的copy 
	copy(p,c);
}

4. clr(p)

用来处理 \(p\) 数组消元的问题的。一方面它会返回当前是否能消元,另一方面它能将所有当前可以消元的位置全部消成 0。(消元过程中不考虑方块下落)

clear
inline bool clr(int p[N][N]){
	//警示后人:不要写错变量名 
	//横向消除 
	bool fl=0;
	for(int j=0;j<7;j++){
		for(int i=0;i<3;i++){
			//当前格如果没有数字就别消了 
			if(!p[i][j]) continue;
			int xx;
			//注意细节边界问题 
			//找到最靠下的xx
			for(xx=i;xx+1<5&&p[xx+1][j]==p[i][j];xx++);
			if(xx-i+1>=3){//可消
				//注意,此时竖向也可能消,所以要额外判断竖直方向。 
				for(int k=i;k<=xx;k++){
					int up=j,dn=j;
					//判好边界条件 
					while(up+1<7&&p[k][j]==p[k][up+1]) up++;
					while(dn>0&&p[k][j]==p[k][dn-1]) dn--;
					if(up-dn+1>=3){//竖向可消 
						for(int l=dn;l<=up;l++) p[k][l]=0;
					}
					else p[k][j]=0;//竖向不可消 
				}
				fl=1;//进行了消除 
			}
		}
	}
	//纵向消除(注释同上) 
	for(int i=0;i<5;i++){
		for(int j=0;j<5;j++){
			if(!p[i][j]) continue;
			int yy;
			//注意细节边界问题 
			for(yy=j;yy+1<7&&p[i][j]==p[i][yy+1];yy++);
			if(yy-j+1>=3){
				for(int k=j;k<=yy;k++){
					int up=i,dn=i;
					//注意不要把k打成j(我当时就这么干的。。。) 
					while(up<4&&p[up][k]==p[up+1][k]) up++;//
					while(dn>1&&p[dn][k]==p[dn-1][k]) dn--;//
					if(up-dn+1>=3){
						for(int l=dn;l<=up;l++) p[l][k]=0;
					}
					else p[i][k]=0;
				}
				fl=1;
			}
		}
	}
	return fl;
}

5. dfs

然后就到了我们的 \(dfs\)。它就是很普通的 \(dfs\),用 \(stp\) 表示当前在走第几步,如果 \(stp > n\),那么直接判断 \(a\) 数组是否为空,是则为正解直接输出。

这里有一处剪枝:如果当前某个颜色的块有 1 个或 2 个时,它们无论如何都消不掉,所以直接返回。

否则我们从小到大枚举每个可能移动的位置。

这里又有一个小优化:因为移动的本质是两个位置的数交换(这里可以思考一下),所以当我们想让 \((i,j)\)\((i+1,j)\) 交换的的话,除非 \(a_{i,j}=0\),否则直接让 \((i,j)\) 右移即可。否则让 \((i+1,j)\) 左移。

然后就可以存询问了,注意要有一个小标记,表示当前是从 \((i,j)\) 左移换过来还是 \((i+1,j)\) 右移换过来。

换过来以后,一要考虑方格第一次坠落的问题,二是考虑多次消除方块并坠落的结果。

(是的,这里的逻辑是这样的:不管你能否消除方块都要坠落一次,否则后续你处理 \(clr\) 时有一部分没有坠下去考虑不到)

(另外,就算你后续不 \(clr\) 后续处理也会有其他的错误。)

(另外,题目提示我们有可能会有多次消除操作,所以我们也要执行多次,并且每一次都需要 \(drop\) 一下,否则理由同上)

然后就可以愉快地往下操作了。

最后不要忘了还原 \(a\) 数组。

dfs
inline void dfs(int stp){
	if(stp>n){
		if(ept(a)){
			for(int i=1;i<=n;i++){
				//注意交换完后真正移动的是哪里 
				if(ans[i].op==-1) printf("%lld %lld %lld\n",ans[i].x+1,ans[i].y,ans[i].op);
				else printf("%lld %lld %lld\n",ans[i].x,ans[i].y,ans[i].op);
			}
			exit(0);
		}
		//注意就算不合法也要先返回 
		return ;
	}
	//判断当前是否已经无解 
	for(int i=0;i<=10;i++){
		sum[i]=0;
	}
	for(int i=0;i<5;i++){
		for(int j=0;j<7;j++){
			sum[a[i][j]]++;
		}
	}
	for(int i=1;i<=10;i++){
		if(0<sum[i]&&sum[i]<3) return ;
	}
	for(int i=0;i<4;i++){
		for(int j=0;j<7;j++){
			//两个位置至少要有一个位置有数,否则不合法 
			if(a[i][j]||a[i+1][j]){
				//b数组一定要开在dfs里面,否则你a回溯回来b也变了 
				int b[N][N];
				copy(b,a);
				//注意语句位置 
				ans[stp]={i,j,(a[i][j]?1:-1)};
				swap(a[i][j],a[i+1][j]);
				drop(a);
				while(clr(a)) drop(a);
				dfs(stp+1);
				//一定要记得把a数组还原回来 
				copy(a,b);
			}
		}
	}
}

好啦,该讲的都讲完了,放一下完整版代码:

我已是完全之P1312
#include<bits/stdc++.h>
#define int long long
using namespace std;

inline int read(){
	int x=0,f=1;char c=getchar();
	while(c<48){
		if(c=='-') f=-1;
		c=getchar();
	}
	while(c>47) x=(x<<1)+(x<<3)+(c^48),c=getchar();
	return x*f;
}

const int N=12;
int n,a[N][N],sum[N],qwq=0;
struct sw{
	int x,y,op;
}ans[N];

inline bool ept(int p[N][N]){
	for(int i=0;i<5;i++){
		for(int j=0;j<7;j++){
			if(p[i][j]) return 0;
		}
	}
	return 1;
}

inline void copy(int p[N][N],int q[N][N]){
	for(int i=0;i<5;i++){
		for(int j=0;j<7;j++){
			p[i][j]=q[i][j];
		}
	}
}

inline void drop(int p[N][N]){
	int c[N][N];
	for(int i=0;i<5;i++){
		int k=-1;
		//k:指针,指向当前c数组存储到哪里了 
		for(int j=0;j<7;j++) c[i][j]=0;
		for(int j=0;j<7;j++){
			if(p[i][j]) c[i][++k]=p[i][j];
			//非0就让它"下落"
		}
	}
	//不要忘了最后的copy 
	copy(p,c);
}

inline bool clr(int p[N][N]){
	//警示后人:不要写错变量名 
	//横向消除 
	bool fl=0;
	for(int j=0;j<7;j++){
		for(int i=0;i<3;i++){
			//当前格如果没有数字就别消了 
			if(!p[i][j]) continue;
			int xx;
			//注意细节边界问题 
			//找到最靠下的xx
			for(xx=i;xx+1<5&&p[xx+1][j]==p[i][j];xx++);
			if(xx-i+1>=3){//可消
				//注意,此时竖向也可能消,所以要额外判断竖直方向。 
				for(int k=i;k<=xx;k++){
					int up=j,dn=j;
					//判好边界条件 
					while(up+1<7&&p[k][j]==p[k][up+1]) up++;
					while(dn>0&&p[k][j]==p[k][dn-1]) dn--;
					if(up-dn+1>=3){//竖向可消 
						for(int l=dn;l<=up;l++) p[k][l]=0;
					}
					else p[k][j]=0;//竖向不可消 
				}
				fl=1;//进行了消除 
			}
		}
	}
	//纵向消除(注释同上) 
	for(int i=0;i<5;i++){
		for(int j=0;j<5;j++){
			if(!p[i][j]) continue;
			int yy;
			//注意细节边界问题 
			for(yy=j;yy+1<7&&p[i][j]==p[i][yy+1];yy++);
			if(yy-j+1>=3){
				for(int k=j;k<=yy;k++){
					int up=i,dn=i;
					//注意不要把k打成j(我当时就这么干的。。。) 
					while(up<4&&p[up][k]==p[up+1][k]) up++;//
					while(dn>1&&p[dn][k]==p[dn-1][k]) dn--;//
					if(up-dn+1>=3){
						for(int l=dn;l<=up;l++) p[l][k]=0;
					}
					else p[i][k]=0;
				}
				fl=1;
			}
		}
	}
	return fl;
}

inline void dfs(int stp){
	if(stp>n){
		if(ept(a)){
			for(int i=1;i<=n;i++){
				//注意交换完后真正移动的是哪里 
				if(ans[i].op==-1) printf("%lld %lld %lld\n",ans[i].x+1,ans[i].y,ans[i].op);
				else printf("%lld %lld %lld\n",ans[i].x,ans[i].y,ans[i].op);
			}
			exit(0);
		}
		//注意就算不合法也要先返回 
		return ;
	}
	//判断当前是否已经无解 
	for(int i=0;i<=10;i++){
		sum[i]=0;
	}
	for(int i=0;i<5;i++){
		for(int j=0;j<7;j++){
			sum[a[i][j]]++;
		}
	}
	for(int i=1;i<=10;i++){
		if(0<sum[i]&&sum[i]<3) return ;
	}
	for(int i=0;i<4;i++){
		for(int j=0;j<7;j++){
			//两个位置至少要有一个位置有数,否则不合法 
			if(a[i][j]||a[i+1][j]){
				//b数组一定要开在dfs里面,否则你a回溯回来b也变了 
				int b[N][N];
				copy(b,a);
				//注意语句位置 
				ans[stp]={i,j,(a[i][j]?1:-1)};
				swap(a[i][j],a[i+1][j]);
				drop(a);
				while(clr(a)) drop(a);
				dfs(stp+1);
				//一定要记得把a数组还原回来 
				copy(a,b);
			}
		}
	}
}

signed main(){
	n=read();
	for(int i=0;i<5;i++){
		//警示后人:7列,输入输满了有可能j=7,所以你 j<7 会 WA on #6 
		for(int j=0;j<8;j++){
			a[i][j]=read();
			if(!a[i][j]) break;
		}
	}
	dfs(1);
	printf("-1");
	return 0;
}
posted @ 2025-11-05 21:14  qwqSW  阅读(17)  评论(0)    收藏  举报