poj2411 Mondriaan's Dream[简单状压dp]
$11*11$格子板上铺$1*2$地砖方案。以前做过?权当复习算了,毕竟以前学都是浅尝辄止的。。常规题,注意两个条件:上一行铺竖着的则这一行同一位一定要铺上竖的,这一行单独铺横的要求枚举集合中出现连续偶数个的1,预处理一下即可。注意数据及时reset。
1 #include<iostream> 2 #include<cstdio> 3 #include<cstring> 4 #include<cmath> 5 #include<algorithm> 6 #include<queue> 7 #define dbg(x) cerr<<#x<<" = "<<x<<endl 9 using namespace std; 10 typedef long long ll; 11 template<typename T>inline char MIN(T&A,T B){return A>B?A=B,1:0;} 12 template<typename T>inline char MAX(T&A,T B){return A<B?A=B,1:0;} 13 template<typename T>inline T _min(T A,T B){return A<B?A:B;} 14 template<typename T>inline T _max(T A,T B){return A>B?A:B;} 15 template<typename T>inline T read(T&x){ 16 x=0;int f=0;char c;while(!isdigit(c=getchar()))if(c=='-')f=1; 17 while(isdigit(c))x=x*10+(c&15),c=getchar();return f?x=-x:x; 18 } 19 const int N=12; 20 ll f[N][1<<N-1]; 21 int n,m,ful,g[1<<N-1]; 22 23 int main(){//freopen("test.in","r",stdin);//freopen("test.out","w",stdout); 24 for(register int i=0;i<(1<<11);++i){ 25 int cnt=0; 26 for(register int j=0;j<11;++j){ 27 if(!((1<<j)&i)){if(cnt&1)break;cnt=0;} 28 else ++cnt; 29 } 30 if(!(cnt&1))g[i]=1; 31 } 32 while(read(n),read(m),n){ 33 if((n*m)&1){printf("0\n");continue;} 34 f[0][ful]=0;ful=(1<<m)-1;f[0][ful]=1; 35 for(register int i=1;i<=n;++i){ 36 for(register int j=0;j<=ful;++j){ 37 f[i][j]=0; 38 for(register int k=0;k<=ful;++k)if(((j|k)==ful)&&(g[j&k])){ 39 f[i][j]+=f[i-1][k]; 40 } 41 } 42 } 43 printf("%lld\n",f[n][ful]); 44 } 45 return 0; 46 }
UPD:以前个人理解这个$0/1$状态设计有问题。应当是$f[i][S]$表示的是当前推导了第$i$行,状态$S$,对应二进制位$1$的表示这一格填了,$0$表示还没填,有$0$的这种情况下是留作下一行使用的(也就是可以由现在没填的情况推向下一行同一列填好的情况),说“表示放下竖着摆的砖的上半部分”的说法是不确切的。这样,用上述正确的状态定义就可以方便解决绝大多数铺砖类题目。再比如:
如果有的格子不准铺,有多少种铺法?最多铺多少个?用上面的定义,可以在当前状态$S$下使用DFS推导出下一行的所有合法方案并更新,不能铺的地方也用$1$来表示。当然,也可以使用轮廓线DP来推,但是好像会被dfs吊打。。但是最好不要用暴力枚举来推,有大量冗余状态。
详细可以见这道题目题解。poj1038 Bugs Integrated, Inc.[状压DP]