【POJ 2411】【Mondriaans Dream】 状压dp+dfs枚举状态
题意:
给你一个高为h,宽为w的矩阵,你需要用1*2或者2*1的矩阵填充它
问你能有多少种填充方式
题解:
如果一个1*2的矩形横着放,那么两个位置都用二进制1来表示,如果是竖着放,那么会对下一层造成影响,所以我们在 这个位置用0来表示,那么下一层的这一列就必须使用1.可以说竖着放是用
0
1 这样来表示
例如上一层的状态是(二进制表示为):11001111 那么我们先对它取反00110000,为什么要这样呢,因为上一层0的位置必须下一层要是1,然后我们在对状态00110000中 的0进行判断,因为这个0表示上一层不对这个位置造成影响,所以我们在这个位置可以接着横着放,也可以竖着放
第一层初始化的时候,状态00110001111,只要状态中连续1的数量是一个偶数这个状态就可以用来初始化第一层dp
代码:
#include<stdio.h> #include<string.h> #include<math.h> #include<algorithm> #include<iostream> using namespace std; #define mem(a) memset(a,0,sizeof(a)) #define mem__(a) memset(a,-1,sizeof(a)) typedef long long ll; const int maxn=15; const int N=(1<<11); const int INF=0x3f3f3f3f; const double blo=1.0/3.0; const double eps=1e-8; ll state[N],dp[maxn][N],tem,n,m; //这个函数就是暴力枚举适合上一层状态的状态 /* 如果一个1*2的矩形横着放,那么两个位置都用二进制1来表示,如果是竖着放,那么会对下一层造成影响,所以我们在 这个位置用0来表示,那么下一层的这一列就必须使用1.可以说竖着放是用 0 1 这样来表示 例如上一层的状态是(二进制表示为):11001111 那么我们先对它取反00110000,为什么要这样呢,因为上一层0的位置必须下一层要是1,然后我们在对状态00110000中 的0进行判断,因为这个0表示上一层不对这个位置造成影响,所以我们在这个位置可以接着横着放,也可以竖着放 */ void dfs(ll i,ll p,ll k) { if(k>=m) //这里要注意,宽为m的矩形,我们只需要使用[0,m-1]这些下标表示,所以就不能出现p&(1<<m)这种 { dp[i][p]+=tem; return; } dfs(i,p,k+1); if(k<=m-2 && !(p&1<<k) && !(p&1<<k+1)) //这里要求的m-2是因为下标范围在[0,m-1],你要是想横着放置一个1*2矩形 //你就需要赵勇两个位置,这两个位置最大是m-1,m-2 dfs(i,p|1<<k|1<<k+1,k+2); //为什么要k+2,因为你把这两个位置变成1之后,要让下标向后移动两位 } int main() { while(~scanf("%lld%lld",&n,&m)) { ll num=0; if(n==0 && m==0) break; mem(dp); for(ll i=0; i<(1<<m); ++i) { ll k=i,len=0,flag=0; while(k) { if(k&1) { len++; } else { if(len!=0 && len%2) { flag=1; break; } len=0; } k>>=1; } if(len!=0 && len%2) { flag=1; } if(flag==0) { state[num++]=i; } } for(ll i=0; i<num; ++i) { dp[1][state[i]]=1; } tem=1; //dfs(1,0,0); //或者你可以不要上面的直接运行这一行代码也可以完成初始化 //我上面的代码就是先找了一些初始化的满足题意的状态 for(ll i = 2; i<=n; i++) { for(ll j = 0; j<1<<m; j++) { if(dp[i-1][j]) tem = dp[i-1][j]; else continue; dfs(i,~j&((1<<m)-1),0); } } printf("%lld\n",dp[n][(1<<m)-1]); } return 0; }

浙公网安备 33010602011771号