又是一道经典的状态压缩dp
开始自己想了一下,总是觉得因为这个小矩形可以竖着放导致没法确定状态如何转移(第i行的小矩形如果竖着放,及可能影响i-1行,也有可能影响i+1行);后面看了别人的题解后,才知道原来我们可以固定小矩形竖着放的时候只能向前放,这样第i行的状态就只能影响i-1行了,也就能顺利的写出状态转移方程啦。
设dp[i][j]表示第i行处于状态j的时候,共有多少种放置方法。
dp[i][j]=sum(dp[i-1][k]),其中状态j和k要能共存,并且j和k要使得第i-1行刚好铺满。
然后就是初始化,初始化时我们将第一行有连续偶数个1的状态赋值为1(因为第一行没有上一行,是不可能出现竖放情况的)其余所有状态为0。
这个题目中较难处理的就是状态转移中j和k必须共存,并且j和k要使得第i-1行刚好铺满的这个条件。
代码中是以j状态为主然后去判断k状态,其实也可以反过来的;具体的处理见代码。
当然这个题目如果就这样赤裸裸的交是会超时的,还可以加点优化:
1)如果n*m为奇数,则是不可能拼凑成功的(因为小矩形的面积是偶数)
2) 总是取n,m中小的那个当成m,这样可以减少状态总数。
3) 因为n,m很小,输入的状态中应该有重复的,所以我们可以把每次的答案记录下来,下次遇到相同的输入时直接输出答案。
还有就是注意一下位运算的优先级(多加括号).
代码如下:
#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
using namespace std;
long long dp[12][3300];
long long ans[12][3300];
int n,m;
bool init(int x)
{
for(int i=0;i<m;)
{
if(x&(1<<i))
{
if(i==m-1)
return false;
if(x&(1<<(i+1)))
i+=2;
else
return false;
}
else i++;
}
return true;
}
bool check(int x,int y)
{
//提取x的每一位
for(int i=0;i<m;)
{
if(x&(1<<i))//如果x的某一位为1
{
if(y&(1<<i))//同时y的那一位也为1,表明x,y这个位置的砖块都是横放的
{//判断一个地方的砖块是否是横放时,我们只需要和它右边的位置进行
//对比,而不要和左边的进行对比(因为只是一个递推的过程)
//这样就已经可以保证正确性(假设当前合理,最后推出矛盾)
if(i==m-1||!(x&1<<(i+1))||!(y&1<<(i+1)))
return false;
i+=2;
}
else i++;//如果y的对应位是0,则表明x的当前位是竖放的(当然有可能不是竖放的)
//如果那样在后面是会出现矛盾的,所以不需在当前进行更复杂的判断
}
else //如果x的当前位为0,则y的对应位一定为1,否则返回false
{
if(y&(1<<i))
i++;
else
return false;
}
}
return true;
}
int main()
{
memset(ans,0,sizeof(ans));
while(scanf("%d%d",&n,&m)&&n)
{
if(n*m&1)
{
printf("0\n");
continue;
}
if(ans[n][m]||ans[m][n])
{
printf("%d\n",ans[n][m]);
continue;
}
if(n<m)
{
int t=n;
n=m;
m=t;
}
int top=(1<<m);
memset(dp,0,sizeof(dp));
for(int i=0;i<top;i++)
{
if(init(i))
dp[1][i]=1;
}
for(int i=2;i<=n;i++)
for(int j=0;j<top;j++)
{
for(int d=0;d<top;d++)
{
if(check(j,d))
dp[i][j]+=dp[i-1][d];
}
}
printf("%lld\n",dp[n][top-1]);
}
return 0;
}