蒙德里安的梦想

蒙德里安的梦想(南昌理工学院ACM集训队)

萌新初识状态压缩DP问题,被此题所困(我看不懂,但我大受震撼.jpg),费劲心思理清后,特写此题解以示纪念。
在这里插入图片描述

题目

原题链接:在这里
在这里插入图片描述
输入样例
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的思想:
1、状态表示: f[i][j] i 表示当前在第 i 列,j 表示从 i - 1 列中伸到第 i 列(因为是1*2的方格)的方格的状态,这个状态可以用二进制数来表示。j 状态位等于1表示上一列有横放格子,即本列有格子捅出来。
2、状态计算:f[i][j] += sum ( f[i-1][k] ) (0 <= k <(1<<n)) 本列的每一个状态都由上列所有“合法”状态转移过来

转移条件:
1、在第 i 列放方格的位置上不能有 i-1 列伸出的方格,即 (j&k)==0
2、因为求的方格为横着摆放的方格种数,因此在计算时还要考虑当第i列摆放完后,第i列中不能存在连续的奇数个空格,即 j|k 中不存在连续奇数个0,这一个条件我们可以通过预处理来得到。

初始化条件:
1、f[0][0] = 1,第0列只能是状态0,无任何格子捅出来。
2、返回值为 f[m][0]。第m + 1列不能有东西捅出来。

代码如下

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

using namespace std;

const int N = 12, M = 1 << N; //M的每一位二进制位储存一种状态

int n, m;
long long f[N][M];// N 代表第N列,M 代表该列的状态, 第 N-1 列是否有方块伸出到第N列,1代表有  
                 // f[i][j]表示 前i-1列的方案数已经确定,从i-1列伸出并且第i列的状态是 j 的所有方案数
                 
bool st[M];//储存每一列上所有的 合法 摆放状态
                         
int main()
{
    while (cin >> n >> m, n || m)
    {
        //预处理 每一列的占位状态里哪些是合法的
        for (int i = 0; i < 1 << n; i ++ ) 
        {
            int cnt = 0;    // 记录连续的0的个数
            st[i] = true;  //记录这个状态被枚举过且可行
            for (int j = 0; j < n; j ++ )//从低位到高位枚举它的每一行 
                if (i >> j & 1)        // 如果 i 的二进制第 j 位为 1 
                {
                    if (cnt & 1) st[i] = false;//如果之前连续0的个数是奇数,竖的方块插不进来,这种状态不行
                    cnt = 0;            //清空计数器,清不清无所谓 
                }
                else cnt ++ ;          //如果不为1,计数器+1
            if (cnt & 1) st[i] = false;//到末尾再判断一下,最后一个1 下面的 0的个数 是否为奇数
        }

        memset(f, 0, sizeof f);//一定要记得初始化成 0,对于每个新的输入要重新计算f[N][M]
        
        f[0][0] = 1;   //按定义这里是:前第-1列都摆好,且从-1列到第0列 伸出来的状态 为0 的方案数
		              //第0列前面没有-1列,即前一列没有横放的方块,所以第0列只有竖放这一种方案 
                    
        for (int i = 1; i <= m; i ++ ) //从每一列开始遍历 
        {
        	for (int j = 0; j < 1 << n; j ++ )     //从0 ~ 2^n依次遍历每一个状态,枚举 当前一列 所有的伸出状态 J 
            {                                      //对于每一个j状态,我们需要找到一个它可以和哪个状态进行转换。 
                for (int k = 0; k < 1 << n; k ++ ) //再枚举 前二列所有的伸出状态 k
                {
            		if ((j & k) == 0 && st[j | k]) //如果它们符合两个转移条件 
                       f[i][j] += f[i - 1][k];    //那么这种状态下它的方案数等于之前每种k状态数目的和    
				}
            }
		}

        cout << f[m][0] << endl; //求的是第m-1行排满,并且第m-1行不向外伸出块的情况
    }
    return 0;
}

这就是我对这道题的理解,感谢观看,点个赞再走呗~
在这里插入图片描述

  • 14
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值