• 【题解】蒙德里安的梦想


    前言

    很经典的状态压缩dp,不过y总的编译器可能有点问题,相同的代码第一遍超时,再提交居然A了,很离谱…

    题目: 蒙德里安的梦想

    题意:

    求把 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
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    输出样例:

    1
    0
    1
    2
    3
    5
    144
    51205
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    思路:

    • 我们会发现,对于任意一种情况,只要它的横向方块确定了,那么竖向的方块只有一种可能,所以我们只需要枚举横向方块的情况就可以。总方案数:等于只放横向方块的合法方案数。
    • 怎么判断是否合法?其实就是看剩余的位置是否能用竖向方块填满,如果可以就说明是合法的。可以每一列来看,如果有连续的奇数个方格没有放,那么说明不合法。
    • 另外在dp时需要保证上一列和下一列不能存在 “冲突” ,也即不能让某列的一个格子被两个方块都放过。
    • 用M来表示每列最多有多少种情况,M是对应的二进制数。因为我们用二进制的形式表示某种情况,所以M的取值范围是[000…0, 111…1]。
    • 这里的某个 “情况” 指的是从上一列凸出来的方块摆放 “情况” 。
    • 因为我们要额外向后询问一列,所以N要设置为12。

    代码:

    #include<iostream>
    #include<string>
    #include<cstring>
    #include<cstdio>
    #include<algorithm>
    #include<cmath>
    
    using namespace std;
    
    const int N = 12, M = 1 << N;
    #define ll long long 
    
    ll f[N][M]; //对于f[i][(10010)B]表示第i列的第一行和第四行有方块凸出来,存的是这种情况下的方案数。
    bool st[M];//记录第i种情况是否符合条件: 也即不存在连续奇数个零
    int n, m;
    
    int main()
    {
    	while (cin >> n >> m, n || m)
    	{
    		memset(f, 0, sizeof f);
    
    		//对于每一列进行预处理,遍历所有情况将含有奇数个连续零的可能剔除。
    		for (int i = 0; i < 1 << n; ++i)
    		{
    			st[i] = true;
    			int cnt = 0;//记录连续零的个数
    			for (int j = 0; j < n; ++j)
    			{
    				//注意是询问i的二进制数中有多少个0,所以应该是右移
    				if (i >> j & 1)
    				{
    					if (cnt & 1)st[i] = false;
    					cnt = 0;
    				}
    				else cnt++;
    			}
    			//如果最后没有遇到零那么不会在循环里判断,所以要特判
    			if (cnt & 1)st[i] = false;
    		}
    
    		//因为第1列不存在前面的列,所以不可能有方块凸出来,于是只有(000...0)B这一种情况
    		f[0][0] = 1;
    		//遍历所有列
    		for (int i = 1; i <= m; ++i)
    		{
    			for (int j = 0; j < 1 << n; ++j)
    			{
    				for (int k = 0; k < 1 << n; ++k)
    				{
    					//st[j | k]表示的是两种情况合并后的情况
    					//注意,比较运算符的优先级高于按位运算符
    					if ((j & k) == 0 && st[j | k])
    					{
    						f[i][j] += f[i - 1][k];
    					}
    				}
    			}
    		}
    		//我们是从0开始枚举的,所以只需要枚举到m-1行合法的情况即可。那么对应的就是第m行没有任何方块凸出来。
    		cout << f[m][0] << endl;
    	}
    
    	return 0;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
  • 相关阅读:
    基于springboot乐器视频学习网站设计与实现(源码齐全可用)
    Idea+maven+spring-cloud项目搭建系列--8整合Zookeeper
    .NET应用系统的国际化-多语言翻译服务
    LeetCode每日一题(1717. Maximum Score From Removing Substrings)
    骨骼动画——2D Animation
    【牛客刷题】每日一练——回文字符串
    算法-手写LRU
    C++11并发编程——多线程
    解决:微软在登录时总是弹出需要家长或监护人同意才能使用该账户并且不断循环?
    python+pytest接口自动化之测试函数、测试类/测试方法的封装
  • 原文地址:https://blog.csdn.net/jhgjhg225/article/details/125613081