☆ [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;
}
posted @ 2018-07-19 13:33  DennyQi  阅读(240)  评论(0编辑  收藏  举报