• 【题解】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;
    }
    
  • 相关阅读:
    英语老师不想让你知道的一些网站分享
    最近三周开发的桌面应用系统
    UML技术沙龙PPT
    Pandas时间处理的一些小方法
    合并函数总结
    开博宣言
    DBGrid中增加一列CHECKBOX
    关于Delphi的Hint
    操作EXCEL
    关于FastReport
  • 原文地址:https://www.cnblogs.com/RemnantDreammm/p/14423232.html
Copyright © 2020-2023  润新知