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

题意:n*m的平面,问有多少种方式能被1 * 2的小条子放满。n, m <= 11。

分析:状压DP。由上一行转到当前行。 定义状态竖着放的上半部分为1,可知上一行&当前行为0,并且上一行|当前行不含奇数长度0。剪枝 + 交换n,m降复杂度。

没想到如何表示状态跟转移
定义竖着木头的上半部分为1,其他为0。 当前行从上一行转移,那么j & k == 0是一个条件。 还有一个条件是j | k,剩下为0的格子没有出现奇数长度的格子。(剩下为0的格子要横着放)
定义好状态后,枚举所有可能的状态,把有奇数长度格子的状态记录下来。(好像不预处理,直接在计算的时候去求也可以,求完记录一下就行)

枚举有奇数长度的空格时,全为0时状态为false。
初始状态dp[0][0] = 1,唯一合法的状态,第0行什么都不放。
然后从1~n转移即可。
m > n可以进行交换。

*/

void solve(){
    int n, m;

    while (cin >> n >> m && n && m){
        if (n * m % 2){
            cout << "0\n";
            continue;
        }
        if (n < m){
            swap(n, m);
        }
        vector<bool> has_odd(1 << m);
        for (int i = 0; i < (1 << m); ++i){
            int cnt = 0;
            int cur = 0;
            for (int j = 0; j < m; ++j){
                if ((i >> j) & 1){
                    cnt |= cur;
                    cur = 0;
                }
                else{
                    cur ^= 1;
                }
            }
            has_odd[i] = (cur | cnt);
        }

        vector<vector<long long>> dp(2, vector<long long> (1 << m));
        int cur = 1;
        dp[0][0] = 1;
        for (int i = 1; i <= n; ++i){
            fill(dp[cur].begin(), dp[cur].end(), 0);
            for (int j = 0; j < (1 << m); ++j){
                for (int k = 0; k < (1 << m); ++k){
                    if ((j & k) == 0 && has_odd[j | k] == false){
                        dp[cur][j] += dp[cur ^ 1][k];
                    }
                }
            }
            cur ^= 1;
        }

        cout << dp[cur ^ 1][0] << '\n';
    }
}
posted @ 2024-01-16 10:02  _Yxc  阅读(24)  评论(0)    收藏  举报