当前位置: 首页 > news >正文

状态压缩DP--------蒙德里安的梦想

> 状态压缩DP--------蒙德里安的梦想

求把 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

#include<bits/stdc++.h>
using namespace std;

const int N=12, M = 1<< N;  

long long f[N][M] ;// 第一维表示列, 第二维表示所有可能的状态

bool st[M];  //存储每种状态是否有奇数个连续的0,如果奇数个0是无效状态,如果是偶数个零置为true。

vector<int > state[M];  //二维数组记录合法的状态
//vector<vector<int>> state(M);  //两种写法等价:二维数组

int m , n;

int main(){

    while(cin>>n>>m, n||m){ //读入n和m,并且不是两个0即合法输入就继续读入

        //第一部分:预处理1
        //对于每种状态,先预处理每列不能有奇数个连续的0
        //为什么可以预处理:对于每一列的每一种状态(j从0-31),它是否合理(不存在连续奇数个0)我们都可以预先计算出来,所以可以预处理 

        for(int i=0; i< 1<<n; i++){//遍历每一列列的所有可能的状态,也就是j的值 0-31

            int cnt =0 ;//记录连续的0的个数

            bool isValid = true; // 某种状态没有奇数个连续的0则标记为true

            for(int j=0;j<n;j++){ //遍历这一列,从上到下

                 if( i>>j &1){  //i>>j位运算,表示i(i在此处是一种状态)的二进制数的第j位; &1为判断该位是否为1,如果为1进入下一层if
                    if(cnt & 1) { //i的第j位为1,看前面连续的0的个数,如果是奇数(cnt & 1为真)则该状态不合法
                    														//奇数的二进制形式最低位全为1 
                        isValid =false;
						break;
                    } 
                    cnt=0; // 既然该位是1,并且前面不是奇数个0(经过上面的if判断),计数器清零。//其实清不清零没有影响
                 }
                 else 
				 	cnt++; //否则的话该位还是0,则统计连续0的计数器++。
            }
            //当直到最后都没有上一列突出来的小尖时,这种情况循环里面无法判断,所以要补充判断 
            if(cnt &1)  
				isValid =false; //最下面的那一段判断一下连续的0的个数

            st[i]  = isValid; //状态i是否有奇数个连续的0的情况,输入到数组st中
        }

        //第二部分:预处理2
        // 经过上面每种状态 连续0的判断,已经筛掉一些状态。
        //下面来看进一步的判断:看第i-2列伸出来的和第i-1列伸出去的是否冲突

        for(int j=0;j< 1<<n;j++){ //对于第i列的所有状态
            state[j].clear(); //清空上次操作遗留的状态,防止影响本次状态。
            for(int k=0;k< 1<<n;k++){ //对于第i-1列的所有状态
                if((j&k )==0 && st[ j | k] ) // 第i-2列伸出来的 和第i-1列伸出来的不冲突(不在同一行,也就是j和k不同时为1) 
                //解释一下st[j | k] 	 
                //已经知道st[]数组表示的是这一列没有连续奇数个0的情况,
                //我们要考虑的是第i-1列(第i-1列是这里的主体)中从第i-2列横插过来的,还要考虑自己这一列(i-1列)横插到第i列的
                //比如 第i-2列插过来的是k=10101,第i-1列插出去到第i列的是 j =01000,
                //那么合在第i-1列,到底有多少个1呢?自然想到的就是这两个操作共同的结果:两个状态或。 j | k = 01000 | 10101 = 11101
                //这个 j|k 就是当前 第i-1列的到底有几个1,即哪几行是横着放格子的

                    state[j].push_back(k);  //二维数组state[j]表示第j行, 
                    //j表示 第i列“真正”可行的状态,如果第i-1列的状态k和j不冲突则压入state数组中的第j行尾部。
                    //“真正”可行是指:既没有前后两列伸进伸出的冲突;又没有连续奇数个0。
            }

        }

        //第三部分:dp开始

        memset(f,0,sizeof f);  //全部初始化为0,因为是连续读入,这里是一个清空操作。类似上面的state[j].clear()

        f[0][0]=1 ;// 这里需要回忆状态表示的定义,按定义这里是:前第-1列都摆好,且从-1列到第0列伸出来的状态为0的方案数。
        //首先,这里没有-1列,最少也是0列。其次,没有伸出来,即没有横着摆的。即这里第0列只有竖着摆这1种状态。

        for(int i=1;i<= m;i++){ //遍历每一列:第i列合法范围是(0~m-1列)
            for(int j=0; j< 1<<n; j++){  //遍历当前列(第i列)所有状态j
                for( auto k : state[j])    // 遍历第i-1列的状态k,如果“真正”可行(第i-1列的所有状态k和第i列的当前状态j满足条件时),就转移
                    f[i][j] += f[i-1][k];    // 当前列的方案数就等于之前的第i-1列所有状态k的累加。
            }
        }

        //最后答案是什么呢?
        //f[m][0]表示 前m-1列都处理完,并且第m-1列没有伸出来的所有方案数。
        //即整个棋盘处理完的方案数

        cout<< f[m][0]<<endl;
    }
}  

相对原文稍加了些注释。

作者:lishizheng
链接:https://www.acwing.com/solution/content/28088/
来源:AcWing

相关文章:

  • 区间DP————石子合并
  • C/C++无穷大的表示 0x7fffffff + 0x7fffffff= 负数
  • 李永乐(一)行列式计算——笔记
  • 李永乐(二)矩阵的概念及运算——笔记
  • C++——using namespace std; 解析
  • 李永乐(三)伴随矩阵、可逆矩阵——笔记
  • 操作系统——考试复习
  • 李永乐(四)初等变换、初等矩阵、分块矩阵——笔记
  • 李永乐(五)向量、线性表出——笔记
  • 李永乐(六)线性相关——笔记
  • 李永乐(七)向量组的秩、矩阵的秩——笔记
  • 李永乐(八)齐次线性方程组——笔记
  • 计算机网络微课堂——第一、二章—概述、物理层
  • 朴素版Dijkstra(最短路径)算法
  • 数据库—应付考试—期末复习
  • CentOS从零开始部署Nodejs项目
  • extract-text-webpack-plugin用法
  • node入门
  • PHP的类修饰符与访问修饰符
  • Redux系列x:源码分析
  • vue脚手架vue-cli
  • 包装类对象
  • 编写高质量JavaScript代码之并发
  • 排序算法之--选择排序
  • 什么软件可以提取视频中的音频制作成手机铃声
  • 在electron中实现跨域请求,无需更改服务器端设置
  • 《天龙八部3D》Unity技术方案揭秘
  • Java数据解析之JSON
  • 整理一些计算机基础知识!
  • ​草莓熊python turtle绘图代码(玫瑰花版)附源代码
  • #pragam once 和 #ifndef 预编译头
  • (LeetCode C++)盛最多水的容器
  • (附源码)spring boot校园拼车微信小程序 毕业设计 091617
  • (实战篇)如何缓存数据
  • (学习日记)2024.04.10:UCOSIII第三十八节:事件实验
  • (转)淘淘商城系列——使用Spring来管理Redis单机版和集群版
  • (轉貼) 2008 Altera 亞洲創新大賽 台灣學生成果傲視全球 [照片花絮] (SOC) (News)
  • ***详解账号泄露:全球约1亿用户已泄露
  • .Net Attribute详解(上)-Attribute本质以及一个简单示例
  • .net和php怎么连接,php和apache之间如何连接
  • .net实现客户区延伸至至非客户区
  • .net之微信企业号开发(一) 所使用的环境与工具以及准备工作
  • .NET中两种OCR方式对比
  • .stream().map与.stream().flatMap的使用
  • @NoArgsConstructor和@AllArgsConstructor,@Builder
  • [].shift.call( arguments ) 和 [].slice.call( arguments )
  • [AndroidStudio]_[初级]_[修改虚拟设备镜像文件的存放位置]
  • [Geek Challenge 2023] web题解
  • [GYCTF2020]Ez_Express
  • [hdu 3746] Cyclic Nacklace [kmp]
  • [IE技巧] IE8中HTTP连接数目的变化
  • [KMP求最小循环节][HDU1358][Period]
  • [LeeCode]-Divide Two Integers 不用乘除的除法运算
  • [LeetCode]Reverse Linked List II
  • [tsai.shen@mailfence.com].faust勒索病毒数据怎么处理|数据解密恢复