【题解】AcWing 291. 蒙德里安的梦想

题目传送门

题目描述

求把N*M的棋盘分割成若干个1*2的的长方形,有多少种方案。

例如当N=2,M=4时,共有5种方案。当N=2,M=3时,共有3种方案。

如下图所示:

输入格式

输入包含多组测试用例。

每组测试用例占一行,包含两个整数N和M。

当输入用例N=0,M=0时,表示输入终止,且该用例无需处理。

输出格式

每个测试用例输出一个结果,每个结果占一行。

数据范围

(1≤N,M≤11)

输入样例:

1 2
1 3
1 4
2 2
2 3
2 4
2 11
4 11
0 0

输出样例:

1
0
1
2
3
5
144
51205
(状态压缩DP) (O(n*(2^n)^2))

(时间复杂度应该没算错qwq

思路

分析

可以看出,当我们横着放的长方形确定时,竖着放的方案必然时确定的。
但是看出很难,怎么看出也不晓得,我也是只不过能理解做法而已

那么我们就可以使用状态压缩(不知道这么说合不合理

实现

状态表示:
f[i][j]表示,第i-1列,向右突出的长方形状态为j时的方案数。

我们使用j的二进制形式来表示状态,有突出即为1

如f[2][5]的话
5 = ((0101)\_2)
Snipaste_2021-02-18_22-06-47.png

Tips: 图中仅为示例表示突出的意思,实际我们存的i是0~m-1

状态转移:

方案数就是相加

但方案不是都合法,所有我们需要做两个判断:

  1. 当前方案与之前方案不冲突,即当前方案所放位置没有被i-1的突起占据
  2. 当前方案选择之后,与前面方案能有偶数个格子为空

一:

原因:
很好理解,不做解释了

做法:
我们可以使用 j & k == 0 来判断是否有冲突。

二:

原因:
首先,为什么是偶数个格子?
因为我们横长方形放完之后,都是竖长方形,那就必须要偶数个格子为空。

做法:
我们可以预处理每个状态是否合法

即求出一个二进制所表示的状态是否有偶数个空。
用 j | k 来表示j 状态与 k状态叠加的结果状态。

注释

long long是因为可能会爆int(我自己没有认真看)
对非法状态的预处理估计放在外面也是可以的。
实际填的是 0~m-1列。
其余放在代码的注释中。

C++ 代码

#include <iostream>
#include <algorithm>
#include <cstdio>
#include <cstring>

using namespace std;

typedef long long LL;

const int N = 15;

int n, m;
LL f[N][1 << N];
bool st[1 << N]; // true:当前状态合法 false:当前状态非法

int main()
{
    while(scanf("%d%d", &n, &m) == 2 && n || m)
    {
        memset(f, 0, sizeof f);
        for(int i = 0; i < 1<<n; i ++) // 对非法状态的预处理
        {
            int cnt = 0; // 空的格数
            st[i] = true;
            for(int j = 0; j < n; j ++)
                if(i>>j & 1)
                {
                    if(cnt & 1) st[i] = false;
                    cnt = 0;
                }
                else cnt ++;
            if(cnt & 1) st[i] = false; // 最后一次还要特一下
        }
        f[0][0] = 1; // 初始化 因为第0列不会有-1列的突出过来所以有一个方案
        for(int i = 1; i <= m; i ++) // 枚举列数
            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]) // 符合条件
                        f[i][j] += f[i-1][k];
        printf("%lld
", f[m][0]); // 最后的方案总数必然是m-1列没有向m列突出
    }
    return 0;
}
原文地址:https://www.cnblogs.com/RemnantDreammm/p/14423232.html