ACWING 291 蒙德里安的梦想

https://www.acwing.com/problem/content/293/
题意

求把N × \times × M的棋盘分割成1 × \times × 2的长方形有多少种方案


思路

由于竖向摆放的长方形情况可被横向摆放的长方形唯一确定,所以考虑横向摆放即可。


定义 d p dp dp数组的含义为已将前 i i i-1列排好,并以 j j j作为状态压缩存储从 i i i-1列伸出到 i i i列的所有方案。对于列数,最大共有 11 11 11列,多开一列以便取答案。对于状态数,共有 2 11 2^{11} 211种状态。

所谓状态压缩,是把具有 m m m位的 b o o l bool bool数组以 m m m位二进制整数表示的方法,也可以使用 S T L STL STL b i t s e t bitset bitset实现。


考虑进行如下优化

  1. 对于横向排布长方形后不能够竖向摆满长方形,即连续的 0 0 0个数为奇数的状态,予以去除。
  2. 对于从 i i i-2列伸出到第 i i i-1列,与从第 i i i-1列伸出到第i列的长方形发生冲突的情况,予以去除。这一步可将第 i i i-2列的状态i和第 i i i-1列的状态j相与,检查是否为 0 0 0,从而判断是否因有重叠部分而不能摆放。

对于 d p [ 0 ] [ 0 ] dp[0][0] dp[0][0],其代表第1列既无伸出,亦没有伸入(因为第1列前并无任何列存在)的情况,所以唯一的摆放方式是将所有长方形竖向摆放, d p [ 0 ] [ 0 ] = 1 dp[0][0]=1 dp[0][0]=1


代码中各数组的含义如下

  1. d p [ N ] [ M ] dp[N][M] dp[N][M],动态规划数组,第一维为列数,第二维为状态。
  2. s t [ M ] st[M] st[M],存储当前考察的状态是否因有连续奇数个 0 0 0而无效。
  3. s t a t e state state,用于记录所有状态组合。


#include <iostream>
#include <cstring>
#include <algorithm>
#include <vector>
using namespace std;
int n, m;
const int N = 12;
const int M = 1 << N;
vector<vector<int>> state(M); // 存储合法状态
long long f[N][M];                  // 第一维代表列 第二维代表状态
bool st[M];                   // 当前列的连续0是否为偶数个

int main()
{
    while (cin >> n >> m, n || m)
    {
        for (int i = 0; i < (1 << n); i++) // 预处理出列中只有偶数个0的状态以确保可被摆放满
        {
            bool is_valid = true;
            int zero_cnt = 0;
            for (int j = 0; j < n; j++)
            {
                if (i >> j & 1)
                {                     // 取出被状态压缩的每一位
                    if (zero_cnt & 1) // 奇数
                    {
                        is_valid = false;
                        break;
                    }
                    zero_cnt=0;
                }
                else 
                    zero_cnt++;
            }
            if (zero_cnt & 1)
                is_valid = false;
            st[i] = is_valid;
        }
        for (int i = 0; i < (1 << n); i++)
        { // state[i]代表第i列状态 而j搜索的是第i-1列的状态
            state[i].clear();
            for (int j = 0; j < (1 << n); j++)
            {
                if ((i & j) == 0 && st[i | j])
                {
                    state[i].push_back(j);
                }
            }
        }
        memset(f, 0, sizeof f);
        f[0][0] = 1; // 第0列没有伸过来的 所以只有竖着一种摆法
        for (int i = 1; i <= m; i++)
        {
            for (int j = 0; j < (1 << n); j++)
            {
                for (auto k : state[j])
                {
                    f[i][j] += f[i - 1][k]; // 从合法状态转移
                }
            }
        }
        cout << f[m][0] << endl; // 答案是使m+1列没有插出来的方案数
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值