zoukankan      html  css  js  c++  java
  • 【题解】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;
    }
    
  • 相关阅读:
    SQL Server 中的事务与事务隔离级别以及如何理解脏读, 未提交读,不可重复读和幻读产生的过程和原因
    微软BI 之SSIS 系列
    微软BI 之SSIS 系列
    微软BI 之SSIS 系列
    微软BI 之SSIS 系列
    微软BI 之SSIS 系列
    微软BI 之SSAS 系列
    微软BI 之SSRS 系列
    微软BI 之SSRS 系列
    配置 SQL Server Email 发送以及 Job 的 Notification通知功能
  • 原文地址:https://www.cnblogs.com/RemnantDreammm/p/14423232.html
Copyright © 2011-2022 走看看