《算法竞赛进阶指南》蒙德里安的梦想 · 状压dp

题解

AcWing 291. 蒙德里安的梦想(《算法竞赛进阶指南》打卡活动)

摆放方块的时候,先放横着的,再放竖着的,其实放完横着的,竖着的也定下来了
总方案数等于只放着的小方块数的合法方案数

如何判断当前方案合法?
每列空余位置,每一块长度均为偶数,即为合法

f i , j f_i,j fi,j 表示第 i i i 列被第 i − 1 i-1 i1 列上横着的 1 × 2 1\times 2 1×2 的方块占用的整列状态 j j j 的方案数
用图表示:
在这里插入图片描述

如果没有多组案例, 11 ∗ 2 11 ∗ 2 11 = 4 e 7 11*2^{11}*2^{11}=4e^7 11211211=4e7 的暴力显然是可以的,

但是,有了就得优化……

优化1:预处理各种状态的合法性

优化2:统计前后两列之间所有合法的状态,抛弃不合法的

这样就可以去掉一个 for


在这里插入图片描述


#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 12;

int n, m;
ll f[N][1 << N];

bool st[1 << N];//当前这一列 剩余部分中连续区间长度是否为偶数
vector<int> state[1 << N];//所有合法状态

//哈密顿是集合的问题
//这里是连通性的问题
int main() {
    ios::sync_with_stdio(0);

    while (cin >> n >> m, n && m) {
        int lim = 1 << n;

        // 状态压缩1:
        // 预处理判断各种状态下每一列剩余部分的合法性
        for (int i = 0; i < lim; ++i) {

            int cnt = 0;
            bool valid = true;
            for (int j = 0; j < n; ++j) {
                if ((i >> j) & 1) {
                    if (cnt & 1) {
                        valid = false;
                        break;
                    }
                    cnt = 0;
                } else cnt++;
            }

            // 务必注意处理边界 ...11000 这种情况
            if (cnt & 1) valid = false;
            st[i] = valid;
        }

        // 状态压缩2:
        // 只统计有效的状态 无效的状态直接扔了
        for (int i = 0; i < lim; ++i) {
            state[i].clear();
            for (int j = 0; j < lim; ++j) {
                if ((i & j) == 0 && st[i | j])
                    state[i].push_back(j);
            }
        }

        memset(f, 0, sizeof(f));

        //第一列开头肯定是没有上一列延伸出来的方块
        f[0][0] = 1;

        for (int i = 1; i <= m; ++i) {
            for (int j = 0; j < lim; ++j) {
                for (auto k:state[j])
                    f[i][j] += f[i - 1][k];
            }
        }
		
		// 统计的是0 ~ m-1列 
		//如果最后一行合法的话 第m列的状态一定为0 第m-1列不可以有横着的方块延伸到第m列上来
        cout << f[m][0] << endl;
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值