又是一道经典的状态压缩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; }