poj2411 Mondriaan's Dream[简单状压dp]

$11*11$格子板上铺$1*2$地砖方案。以前做过?权当复习算了,毕竟以前学都是浅尝辄止的。。常规题,注意两个条件:上一行铺竖着的则这一行同一位一定要铺上竖的,这一行单独铺横的要求枚举集合中出现连续偶数个的1,预处理一下即可。注意数据及时reset。

  #include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<queue>
#define dbg(x) cerr<<#x<<" = "<<x<<endl
using namespace std;
typedef long long ll;
template<typename T>inline char MIN(T&A,T B){return A>B?A=B,:;}
template<typename T>inline char MAX(T&A,T B){return A<B?A=B,:;}
template<typename T>inline T _min(T A,T B){return A<B?A:B;}
template<typename T>inline T _max(T A,T B){return A>B?A:B;}
template<typename T>inline T read(T&x){
x=;int f=;char c;while(!isdigit(c=getchar()))if(c=='-')f=;
while(isdigit(c))x=x*+(c&),c=getchar();return f?x=-x:x;
}
const int N=;
ll f[N][<<N-];
int n,m,ful,g[<<N-]; int main(){//freopen("test.in","r",stdin);//freopen("test.out","w",stdout);
for(register int i=;i<(<<);++i){
int cnt=;
for(register int j=;j<;++j){
if(!((<<j)&i)){if(cnt&)break;cnt=;}
else ++cnt;
}
if(!(cnt&))g[i]=;
}
while(read(n),read(m),n){
if((n*m)&){printf("0\n");continue;}
f[][ful]=;ful=(<<m)-;f[][ful]=;
for(register int i=;i<=n;++i){
for(register int j=;j<=ful;++j){
f[i][j]=;
for(register int k=;k<=ful;++k)if(((j|k)==ful)&&(g[j&k])){
f[i][j]+=f[i-][k];
}
}
}
printf("%lld\n",f[n][ful]);
}
return ;
}

UPD:以前个人理解这个$0/1$状态设计有问题。应当是$f[i][S]$表示的是当前推导了第$i$行,状态$S$,对应二进制位$1$的表示这一格填了,$0$表示还没填,有$0$的这种情况下是留作下一行使用的(也就是可以由现在没填的情况推向下一行同一列填好的情况),说“表示放下竖着摆的砖的上半部分”的说法是不确切的。这样,用上述正确的状态定义就可以方便解决绝大多数铺砖类题目。再比如:

如果有的格子不准铺,有多少种铺法?最多铺多少个?用上面的定义,可以在当前状态$S$下使用DFS推导出下一行的所有合法方案并更新,不能铺的地方也用$1$来表示。当然,也可以使用轮廓线DP来推,但是好像会被dfs吊打。。但是最好不要用暴力枚举来推,有大量冗余状态。

详细可以见这道题目题解。poj1038 Bugs Integrated, Inc.[状压DP]

上一篇:Linux内核分析 第二周


下一篇:Linux内核分析第二周:操作系统是如何工作的