【蓝桥杯】 2017年决赛C/C++B组 #2 磁砖样式 (DFS、集合去重)—— 酱懵静

2017决赛真题C/C++B组 磁砖样式

问题描述
小明家的一面装饰墙原来是 3╳10 的小方格。
现在手头有一批刚好能盖住2个小方格的长方形瓷砖。
瓷砖只有两种颜色:黄色和橙色。

小明想知道,对于这么简陋的原料,可以贴出多少种不同的花样来。
小明有个小小的强迫症:忍受不了任何2*2的小格子是同一种颜色。
(瓷砖不能切割,不能重叠,也不能只铺一部分。另外,只考虑组合图案,请忽略瓷砖的拼缝)
显然,对于 2╳3 个小格子来说,口算都可以知道:一共10种贴法,如图所示:
在这里插入图片描述
但对于 3╳10 的格子呢?肯定是个不小的数目,请你利用计算机的威力算出该数字。
注意:你需要提交的是一个整数,不要填写任何多余的内容(比如:说明性文字)



---分割线---



作为一道填空题,本题的数据量是很小的,所以第一个想到的思路毫无疑问是枚举。
而在这里的枚举显然是需要配合着搜索算法来的。为了能够将所有的情况都考虑到,这里最好采用深度优先搜索(DFS)来对这个图进行一个遍历。本题的唯一难点在于怎样将题目中说到的行走方式进行一个转换,也就是将“铺瓷砖”这一行为进行一个合理的建模,以使得能够以数学的方式来表达,从而方便我们编程。
首先,根据题目中“墙”的特征,我们应该要想到需要采用一个邻接矩阵(mp[x][y])来存储这个“墙”
然后再仔细观察题目中给出的长方形瓷砖的特征,我们可以知道,其有两种摆放的方式(横向和纵向)。对应过来,我们的代码也应该是两种行走方向——横向和纵向。而这个提示,实际上指示了我们在dfs中的两种行走方式。
这里和前面提到的DFS的不同的在于:
1.本题中走一步,实际上要走两个点(或许横,或许纵)
2.本题没有障碍
3.本题需要走完图中的所有点,且不能有重复
这三个限制,实际上加大了我们的搜索次数和难度。为什么?因为你除了需要考虑将所有的点走完,还要保证你行走的路径的一致性。
这里有一点很重要的是:在你的dfs中,你的行走方式一定要是有序的!!!否则就会有不能走完整个图的风险。我在代码中的行走顺序为:先横向走,再纵向走。也就是说在dfs中,我的每次遍历都会优先考虑横向行走,然后再考虑纵向。
再有一点是,在代码中,虽然说走一步实际上要走两个点,可我们的dfs依然是一个点一个点走的,如此,配合着上面的固定行走顺序,就保证了不可能存在漏点未走的情形。即:保证了图能够完整地遍历完。
至此,就只剩下一个问题了——颜色。关于颜色,其实我们可以通过对mp[x][y]的不同赋值来区分。本题的方案是:mp[x][y]=-1代表当前点未走,mp[x][y]=1代表给当前点涂上橙色,mp[x][y]=2代表给当前点涂上黄色。
注:这样的涂色赋值方案不是随便来的,题中说到小明的强迫症:忍受不了任何2╳2的小格子是同一种颜色。那么这样赋值来区分颜色有一个好处是,当存在一个2╳2的小格子是同一个颜色的时候,这2╳2的小格子之和必然是4的整数倍!(如:sum( mp[1][1]=1,mp[1][2]=1,mp[2][1]=1,mp[2][2]=1 )==4 )否则不可能是4的整数倍!(如:sum( mp[1][1]=1,mp[1][2]=2,mp[2][1]=1,mp[2][2]=2 )==6 )
表待在代码中即为:

for(int i=1;i<m;i++)			//检查2*2的格子是否存在颜色一致的 
		for(int j=1;j<n;j++)
			if((mp[i][j]+mp[i+1][j]+mp[i][j+1]+mp[i+1][j+1])%4==0) 
				return false;	//返回false即代表存在某个2╳2的小格子是同一个颜色,所以小明的强迫症犯了



---分割线---



下面给出本题的完整代码:

#include<iostream>
#include<cstring>
#include<map>
using namespace std;

map<string,int> dict;		//键值对
const int MAX=15;			//阈值
int mp[MAX][MAX];			//模拟整个墙的矩阵
int m,n;					//m行n列
int ans;					//最后答案

bool judge()
{
	for(int i=1;i<m;i++)	//检查2*2的格子是否存在颜色一致的 
		for(int j=1;j<n;j++)
			if((mp[i][j]+mp[i+1][j]+mp[i][j+1]+mp[i+1][j+1])%4==0) 
				return false;
	string str;
	for(int i=1;i<=m;i++)	//检测当前dfs产生的结果是否已经走过 
		for(int j=1;j<=n;j++)
			str+='0'+mp[i][j];
	if(!dict.count(str)) 	//用集合判是否有重(也可用这个序列导出一个哈希值,并以此判重)
	{
		dict[str]=1;
		return true;
	}
	return false;
}
void dfs(int x,int y)
{
	if(mp[x][y]==-1)				 //未上色则前进 
	{
		if(y+1<=n && mp[x][y+1]==-1){//横向摆放 
			for(int i=1;i<=2;i++)	 //两种颜色可涂
			{
				mp[x][y]=mp[x][y+1]=i;
				dfs(x,y+1);			//如果能进入到本if语句,说明接下来必走下一列
			}
			mp[x][y]=mp[x][y+1]=-1;	//恢复现场
		}
		if(x+1<=m && mp[x+1][y]==-1){//纵向摆放 
			for(int i=1;i<=2;i++)
			{
				mp[x][y]=mp[x+1][y]=i;
				if(y==n) dfs(x+1,1);//为了填完整个图,你的每一次行走都应该按照某个固定的逻辑顺序。在这里我的顺序是先横着填,然后竖着。因此在“纵向摆放”的这个大前提下,需要先判断是否可横着走。如果可以,那么一定要横着填。否则就和五代十国一样,社会无秩序,天下大乱!
				else dfs(x,y+1);
			}
			mp[x][y]=mp[x+1][y]=-1;
		} 
	}
	else
	{
		if(x==m && y==n) {	//涂满 
			if(judge()) ans++;
			return;
		}
		if(y==n) dfs(x+1,1);
		else dfs(x,y+1);
	} 
}

int main()
{
	memset(mp,-1,sizeof(mp)); 
	cin>>m>>n;
	dfs(1,1);
	cout<<ans<<endl;
	return 0;	
}

发布了30 篇原创文章 · 获赞 67 · 访问量 3040

猜你喜欢

转载自blog.csdn.net/the_ZED/article/details/100936267