蒙德里安的梦想(状态压缩DP)

 

 分析: 

  1. 所谓的状态压缩DP,就是用二进制数保存状态。为什么不直接用数组记录呢?因为用一个二进制数记录方便作位运算。

  2. 本题等价于找到所有横放 1 X 2 小方格的方案数,因为所有横放确定了,那么竖放方案是唯一的。

  3. 用f[i][j]记录第i-1列已经充满且第i列第j个状态。j状态位等于1表示上一列有横放格子,本列有格子捅出来。转移方程很简单,本列的每一个状态都由上列所有“合法”状态转移过来f[i][j] += f[i - 1][k]

  4. 两个转移条件: i 列和 i - 1列同一行不同时捅出来 ; 本列捅出来的状态j和上列捅出来的状态k求或,得到上列是否为奇数空行状态,奇数空行不转移。

  5. 初始化条件f[0][0] = 1,第0列只能是状态0,无任何格子捅出来。返回f[m][0]。第m + 1列不能有东西捅出来。

 

#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;

const int N = 12, M = 1 << N;

bool st[M];

long long f[N][M];

int main() {
    int m, n;
    while(cin >> m >> n && (m || n)) {
        
        for(int i = 0; i < 1 << m; i++) {
            int count = 0;
            st[i] = true;
            for(int j = 0; j < m; j++) {
                if(!(i & (1 << j))) {
                    count++;
                } else {
                    if(count & 1) {
                        st[i] = false;
                    }
                    count = 0;
                }
            }
            if(count & 1) st[i] = false;
        }
        memset(f,0,sizeof f);
        f[0][0] = 1;
        
        for(int i = 1; i <= n; i++) {
            for(int j = 0; j < 1 << m; j++) {
                for(int k = 0; k < 1 << m; k++) {
                    if((j & k) == 0 && st[j | k]) { //st[j | k] : 表示从k转移到j 的状态时 i-1列是合法的(i-1列可以被完全填充)
                        f[i][j] += f[i-1][k];
                    }
                }
            }
        }
        cout << f[n][0] << endl;
    }
    return 0;
}

 

posted @ 2020-07-31 11:05  Sexyomaru  阅读(196)  评论(0编辑  收藏  举报