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