版权声明:转载注明下出处就行了。 https://blog.csdn.net/LJD201724114126/article/details/84799199
题目链接:哆啦A梦传送门
题意:给出n*m的方块,让你用1*2的方块去填满它,问:有多少种不同的方案?
题解:
参考链接:https://blog.csdn.net/u014634338/article/details/50015825
看完题解,大概就这几个核心点。
1,我们用二进制来表示横放和竖放,如图所示:
2,我们已经知道横放和竖放的规则,那么我们只需枚举二进制,判断第i+1行是否跟第i行产生不兼容。
首先我们先初始化第1行。
然后我们可以分类下判断兼不兼容的情况:
(i+1,j) (表示第i+1行,j列)
(i+1,j)为1时,(i,j)为0,满足条件(竖放)
(i,j) 为1,必须(i+1,j+1)和(i, j+1) 必须为1才满足条件(两个横放)
(i+1,j)为0时,(i,j)为1,满足条件(横放与竖放)
其它情况都为不兼容。
代码:
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
typedef long long LL;
LL dp[15][1<<12];
int n,m;
///设置初始状态
bool init(int status)
{
for(int j=0;j<m;) ///前j-1列符合要求,对第j列进行判断
{
if(status&(1<<j)) ///第j列为1
{
if(j==m-1) return false; ///j为最后一列
if(status&(1<<(j+1)))///第j列和第j+1都为1,则表示横放,可行,考虑j+2列
j+=2;
else ///第j列为1,j+1列为0,不可行
return false;
}
else j++; ///第j列为0,则为竖放,可行
}
return true;
}
///判断上一次的状态和本次状态是否兼容
bool check(int now,int pre)
{
for(int j=0;j<m;)
{
if(now&(1<<j)) ///第i行第j列为1
{
if(pre&(1<<j)) ///第i-1行第j列也为1,那么第i行必然是横放
{
///第i行和第i-1行的第j+1列都必须是1,否则是非法的
if(j==m-1||!(now&(1<<(j+1)))||!(pre&(1<<(j+1))))
return false;
else j+=2;
}
else j++; ///第i-1行第j列为0,说明第i行第j列是竖放
}
else{ ///第i行第j列为0,那么第i-1行的第j列应该是已经填充了的,必须为1
if(pre&(1<<j)) j++;
else return false;
}
}
return true;
}
void solve()
{
int tot=(1<<m)-1;
memset(dp,0,sizeof(dp));
for(int i=0;i<=tot;i++){ ///初始化第1行
if(init(i))
dp[1][i]=1;
}
for(int i=2;i<=n;i++)
{
for(int j=0;j<=tot;j++) ///第i行的状态
{
for(int k=0;k<=tot;k++) ///第i-1行的状态
{
if(check(j,k))
dp[i][j]+=dp[i-1][k];
}
}
}
printf("%lld\n",dp[n][tot]);
}
int main()
{
while(~scanf("%d%d",&n,&m))
{
if(n==0&&m==0) break;
if(n&1&&m&1) { ///都为奇数
printf("0\n");continue;
}
if(m>n) swap(n,m); ///交换下,使得m较小,减少状态量
solve();
}
return 0;
}
总结下:这里用二进制来表示横放与竖放,就用得很巧妙,我们要想要用状态压缩去做题,必须要找到适合题目要求的独特二进制表示,
这里横放用1 1 ,竖放用0|1 ,就是独特的二进制来表示,之后我们就去枚举,看是否冲突。
我的标签:做个有情怀的程序员。