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

算法1

状压DP,时间复杂度:\(O(n * 2 ^ {2M})\)

C++ 代码

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

using namespace std;

const int N = 15, M = 1 << 11;

//当确定所有竖直长方形的位置后,水平长方形摆放的位置是固定的,所以总方案个数等于摆放所有竖直长方形的个数。
int n, m;
bool st[M]; // 在某一行摆放了竖直长方形后,还能摆放水平的长方形。要满足条件,必须连续的空余部分是偶数。
long long f[N][M]; //f[i][j] 在第i行,放竖直长方形时的状态的集合。

int main() {
    while (cin >> n >> m, n || m) {
        memset(f, 0, sizeof f);
        
        for (int i = 0; i < 1 << m; ++i) {
            st[i] = true; 
            int cnt = 0;
            for (int j = 0; j < m; ++j) { //从左列边界开始
                if (i >> j & 1) { //摆放了竖直长方形,则判断之前连续的空余部分是否是偶数
                    if (cnt & 1) st[i] = false; //不是偶数,则说明,该状态无效
                    cnt = 0; //连续空余部分的数量清0
                } else cnt++; //当没有摆放竖直长方形时,空余部分累加
            }
            if (cnt & 1) st[i] = false; //到右列边界时还需要判断一下
        }
        
        f[0][0] = 1;
        for (int i = 1; i <= n; ++i) { //枚举每一行
            for (int j = 0; j < 1 << m; ++j) { //枚举当前行的所有状态
                for (int k = 0; k < 1 << m; ++k) { //枚举上一行的所有状态
                    if ((j & k) == 0 && st[j | k]) //上一行放的竖直长方形与当前要放的竖直长方形不重叠。以及摆放完后当前行连续空余的部分有效。
                        f[i][j] += f[i - 1][k];
                }
            }
        }
        
        cout << f[n][0] << endl; //最后输出在第n行不摆放竖直小方块时的方案数。
    }
    return 0;
}
posted @ 2020-10-25 16:03  咕咕咕OI  阅读(102)  评论(0)    收藏  举报