状压DP

Posted on 2023-04-04 22:00  lyc2002  阅读(13)  评论(0编辑  收藏  举报

[acwing]291. 蒙德里安的梦想

/*
	横放的方案数就等于总方案数,因为横着放完后,再竖着放是唯一的

	dp[i][j] 表示第 i 列状态为 j 的方案数
	状态为 j 是指:各行用 0 或 1 表示摆放状态
			     :若某行为 0,表示竖放或由前一列伸出
			     :若某行为 1,表示横放并向后一列伸出
			   
	dp[i][j] = ∑ dp[i - 1][k],要求 k 和 j 要兼容
	k 和 j 要兼容表示:k | j 不能有连续奇数个 1,否则的话竖着无法放下
		        :k & j == 0 必须成立,不然都横着放有冲突
				   
	dp[0][0] = 1
	
	dp[m][0]
*/
#include <iostream>
#include <cstring>

using namespace std;

typedef long long LL;

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

int n, m;
LL dp[N][M];
bool st[M]; // k | j 的结果状态是否兼容

int main()
{
    while (cin >> n >> m && (n || m))
    {
        for (int i = 0; i < 1 << n; i++) // 遍历所有的结果状态
        {
            st[i] = true;
            int cnt = 0; // 此状态连续的 0 的个数
            for (int j = 0; j < n; j++)
            {
                if (i >> j & 1) // 如果遇到 1
                {
                    if (cnt & 1) // 如果 cnt 是奇数
                    {
                        st[i] = false;
                        break;
                    }
                    cnt = 0;
                }
                else // 如果遇到 0
                    cnt++;
            }
            if (cnt & 1) st[i] = false; // 处理高位 0 
        }
        
        memset(dp, 0, sizeof(dp)); // 多次询问每次都要清空 dp 数组
        dp[0][0] = 1;
        for (int i = 1; i <= m; i++) // 枚举 m 列
            for (int j = 0; j < 1 << n; j++) // 枚举第 i 列的状态
                for (int k = 0; k < 1 << n; k++) // 枚举第 i - 1 列的状态
                {
                    if ((j & k) == 0 && st[j | k])
                        dp[i][j] += dp[i - 1][k];
                }
                
        cout << dp[m][0] << endl;
    }
    
    return 0;
}