题目链接:poj 2411
题目大意:对于一个n*m的矩阵,可以放1*2的牌,横着放或者竖着放,问有多少种放的方法。
我们考虑每一行是把牌横着放还是竖着放,显然竖着放的时候会影响到下一行,所以我们把竖着放的位置用二进制的1表示,并且是表示这个位置是竖着放的牌的上半部分(可能有些绕 仔细理解一下)
那么对于一行的状态我们用一个二进制的数如上面的说法表示,如何转移呢,要满足一下两点:
- 上一行的状态和本行的状态&运算为0 这个比较好理解 因为上一行为1的位置表示竖着放的牌的上半部分,在这一行肯定就是下半部分了,我们用二进制位的0表示,同理,另外这一行为1的位置肯定上一行不能为1。
- 上一行的状态和本行的状态进行|运算后得到的二进制数得满足:0都是偶数个连续出现的 这点我们仔细想想也能明白,上一行和本行或运算以后就可以表示本行哪些位置的牌是竖着放了(这些位置是1) 那么剩下的位置就是横着放的,肯定得是偶数个0才满足
对于满足条件2的这些数我们可以预处理一下 然后以行为阶段就可以进行dp了
表示前 i 行,当前行状态为 j,可以得到的方法数
我们枚举上一行的状态 k 对于满足条件的状态
最终我们需要的答案是 (最后一行显然不需要一个竖着放的牌的上半部分)
#include<cstdio>
#include<cstring>
using namespace std;
typedef long long ll;
bool check[1<<11];
ll f[12][1<<11];
int n,m;
int main(){
while(scanf("%d%d",&n,&m),n||m){
memset(check,false,sizeof(check));
for(int i = 0; i < (1<<m); i++){//预处理部分
int flag = 1,cnt = 0;
for(int j = 0; j < m; j++){
if(i>>j&1){
if(cnt%2){
flag=0;
break;
}
cnt=0;
}else cnt++;
}
if(cnt%2) flag=0;
check[i]=flag;
}
//for(int i = 1; i < (1<<m); i++) printf("i=%d che[i]=%d\n",i,check[i]);
f[0][0]=1;
for(int i = 1; i <= n; i++){
for(int j = 0; j < (1<<m); j++){
f[i][j]=0;
for(int k = 0; k < (1<<m); k++)
if(((j&k)==0)&&check[j|k]){
f[i][j]+=f[i-1][k];
}
}
}
printf("%lld\n",f[n][0]);
}
return 0;
}