题目链接:http://poj.org/problem?id=2411
题意:
用1*2的骨牌填充一个n*m的网格。
求方案数。
算法:
这道题用普通的状态压缩DP的话,打表也可以过。
也就是用一个m位二进制数,0表示上一行的这个位置未被覆盖,1表示被覆盖。
然后逐行转移。
不过我用朴素的状态压缩优化到死的时候,也就知道这题一定有别的解法。
先来介绍一种用二进制枚举子集的方法,是zkc同学之前教给我的。
假设有一个集合S,它的元素是0~(k-1)中的一部分。
用一个k位二进制数mask代表这个集合。
mask的第x位为1表示x在这个集合中,
否则x不在这个集合中。‘
那么下面这个语句可以不重不漏的枚举出S的所有子集
for(int mask1=mask; mask1>=0; mask1=(mask1-1)&mask)
原理也比较简单。
当某一次-1操作使得原本不该出现1的地方出现了1。
也就是说,出现了一个不合法的后缀(这个后缀肯定是一连串的1)
那么&操作就相当于是在合法的前缀不变的情况下,这些不合法的后缀“跨过去”了。
直观地看,就是把这些1“减去”了。
在这个过程中,一定没有漏掉任何合法的情形,
因为这些1不减去的话肯定是不合法的。
这道题与以上的情形正好相反。
当我们知道上一行的覆盖情况,那么上一行未被覆盖的格子下一行必定要覆盖,
也就是有些格子是必取的。
那么可以用下面这个语句枚举出下一行的所有情形。
for(int mask1=mask; mask1<(1<<m); mask1=(mask 1+1)|mask)
当然,要求除了与上一行相接的格子(竖直放置骨牌)外,
其它被覆盖的格子要是两两一组的。
这个判断一下就可以了。
代码如下:
#include<cstdio> #include<iostream> #include<cstdlib> #include<cstring> #include<climits> #include<cmath> #include<algorithm> #include<queue> #include<vector> #include<stack> #include<set> #include<map> #define INF 0x3f3f3f3f using namespace std; const int MAXN=11; long long d[MAXN+1][MAXN+1][1<<MAXN]; int m; bool judge(int mask) { while(mask) { if(mask&1) { if((mask&3)!=3) { return false; } mask>>=2; } else { mask>>=1; } } return true; } int main() { memset(d,0,sizeof(d)); for(m=1; m<=11; m++) { d[0][m][(1<<m)-1]=1LL; for(int i=0; i<11; i++) { for(int mask1=0; mask1<(1<<m); mask1++) { int mask=(~mask1)&((1<<m)-1); for(int mask2=mask; mask2<(1<<m); mask2=(mask2+1)|mask) { if(judge(mask2^mask)) { d[i+1][m][mask2]+=d[i][m][mask1]; } } } } } int a,b; while(scanf("%d%d",&a,&b),(a||b)) { printf("%I64d\n",d[a][b][(1<<b)-1]); } return 0; }
PS:
在网上看到纳米兄的解题报告是用插头DP中的轮廓线解决的,深感不明觉厉。
不过最近确实事情太多,过一段时间好好的学习一下插头DP,再来重做这题。