☆ [POJ2411] Mondriaan's Dream 「状压DP」
传送门 >Here<
题意:用1*2的砖块铺满n*m的地板有几种方案
思路分析
状压经典题!
我们以$f[i][j]$作为状态,表示第i行之前全部填完并且第i行状态为j(状压)时的方案数。
我们考虑,对于一个格子,一块砖有3种方法。
(一):横着放。对下一行没有任何影响
(二):竖着放,并且当前这一格作为砖块的下层。那么对下一行也没有任何影响
(三):竖着放,并且当前这一格作为砖块的上层。这种情况对下一行很明显是有影响的。
综上,只有情况3是对下一行有影响的。
所以我们需要一种方法来区分前两种情况和第三种情况。
对于第一种情况,我们把横着的两个位置都染上1,第二种情况的所在格子染成1。唯有第三种情况的时候把当前格子染成0.
所以基本思路就有了,枚举行,然后枚举上下两个状态进行状态转移。如果这两种状态能够吻合(即不冲突),就累计方案数。
那么怎么进行判断是否冲突呢?
首先,一上一下不能再同一个位置出现0——因为这就意味着有两个竖放砖块的上层。
另外,如果一上一下都是1,意味着是两个横放砖块——相邻的两个格子也必须是1.
这样做单独一个的复杂度是4千万左右不会爆,但是考虑到题目有多组询问……打表就好了。
Code
这个代码是表的生成器。
/*By QiXingzhi*/ #include <cstdio> #include <cstring> using namespace std; typedef long long ll; #define int ll const int N = 10010; const int INF = 1061109567; int h,w,lim; int f[15][N]; inline bool Check(int lst, int cur, int m){ for(int i = 0; i < m;){ if(!(lst & (1 << (i))) && !(cur & (1 << (i)))) return 0; else if((lst & (1 << (i))) && (cur & (1 << (i)))){ if((lst & (1 << (i+1))) && (cur & (1 << (i+1)))){ i += 2; continue; } else return 0; } else{ ++i; continue; } } return 1; } inline int solve(int n, int m){ memset(f, 0, sizeof(f)); lim = ((1<<(m))-1); f[0][lim] = 1; for(int i = 1; i <= n; ++i) for(int j = 0; j <= lim; ++j) if(i == 1){ if(Check(lim, j, m)) ++f[1][j]; } else{ for(int k = 0; k <= lim; ++k) if(Check(k, j, m)) f[i][j] += f[i-1][k]; } return f[n][lim]; } main(){ while(1){ scanf("%d %d", &h, &w); if(!h && !w) break; if((h & 1) && (w & 1)){ printf("0\n"); continue; } printf("%lld\n",solve(h,w)); } return 0; }