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

C++

蒙德里安的梦想

/*
问题描述:
    求把 N×M 的棋盘分割成若干个 1×2 的长方形,有多少种方案。
    例如当 N=2,M=4 时,共有 5 种方案。当 N=2,M=3 时,共有 3 种方案。
    数据范围: 1 ≤ N,M ≤ 11

解题思路:
    本题是一个状态压缩的动态规划问题,相邻状态之间存在着递进的关系。
    f[i][state] 表示第 i 列 state 状态下的方案数,state 是一个二进制的状态编码,该二进制编码
    对应的是 0 - j 行的状态,1 表示这是一个横着放长方形的起始点。
    那么,f[i-1][state0] -> f[i][state1] 合法的方案为:
        condition1: (state0 & state1) == 0,这是为了保证横着放的长方形版块不会冲突
        condition2: state = (state1 | state2),这样 state 必须保证自身 0 是连续的偶数,因为我们要竖着放板子了。
    那么在循环过程着中,state0 和 state1 如何配对:
        plan1. 暴力枚举,然后使用 condition1 和 condition2 筛选掉不合法的方案
        plan2. 拿到 state2,dfs寻找合适的 state1,但是这样可能会爆栈,dfs快在前面不合法,后面就可以剪枝,不搜。
    考虑到爆栈,我们细谈 plan1,首先是对列 i 遍历,从小到大,进而遍历 state1,遍历寻找合法的 state0 ,加到当前位置
        N * (2 ^ N) * (2 ^ N) * (判断state0 state1合法的复杂度)
        考虑判断合法复杂度的问题,condition1 -> O(1)
                              condition2 -> O(N),但是状态不多,完全可以打表完成,因此优化到 O(1)
        --> 最终复杂度 O(N * 2 ^ (2N)) 大概 1e7
    
    不妨再想一想, 对于 state1 完全也可以是打一个 state0 的表格,毕竟他是重复计算了最外层循环 N 次
注意点:
    全部 Long Long,输入输出,函数定义
    打表是个好东西
 */
#include <cstdio>
#include <iostream>
#include <cstring>
#include <algorithm>
#include <cmath>
#include <vector>
#include <queue>
using namespace std;

typedef long long LL;
const int N = 12;
int n, m;
LL f[N][1 << N];
int st[1 << N];  // 用于记录状态的有效性


LL solution() {
    // 首先打表 st[]
    int continuous_zero_cnt = 0;
    for (int state = 0; state < (1 << n); state ++ ) {
        continuous_zero_cnt = 0;
        st[state] = true;
        for (int j = 0; j < n; j ++ ) {
            if (((state >> j) & 1) == 0) {
                continuous_zero_cnt += 1;
            } else {
                if ((continuous_zero_cnt & 1) == 1) {   // odd continuous cnt
                    st[state] = false;
                }
                continuous_zero_cnt = 0;
            }
        }
        if ((continuous_zero_cnt & 1) == 1) {   // odd continuous cnt
            st[state] = false;
        }

    }

    // 打表合法的 state1 -> state0
    vector<int> legal_state_match[1 << N];
    for (int state1 = 0; state1 < (1 << n); state1 ++ ) {
        for (int state0 = 0; state0 < (1 << n); state0 ++ ) {
            if ((state0 & state1) == 0 && st[state0 | state1]) {
                legal_state_match[state1].push_back(state0);
            }
        }
    }

    // dp initialize
    memset(f, 0, sizeof f);
    f[0][0] = 1;
    for (int i = 1; i <= m; i ++ ) {
        for (int state1 = 0; state1 < (1 << n); state1 ++ ) {
            for (auto state0 : legal_state_match[state1]) {
                f[i][state1] += f[i-1][state0];
            }
        }
    }

    return f[m][0];
}

int main()
{
    long long res;
    while (scanf("%d%d", &n, &m), n != 0 || m != 0) {
        res = solution();
        printf("%lld\n", res);
    }

    return 0;
}

posted @ 2022-07-10 16:38  lucky_light  阅读(49)  评论(0编辑  收藏  举报