瓷砖覆盖地板c语言程序,瓷砖覆盖地板的扩展问题
《编程之美》的4.2中有一个瓷砖覆盖地板的问题:
某年夏天,位于希格玛大厦四层的微软亚洲研究院对办公楼的天井进行了一次大 规模的装修.原来的地板铺有 N×M 块正方形瓷砖,这些瓷砖都已经破损老化了,需要予以 更新.装修工人们在前往商店选购新的瓷砖时,发现商店目前只供应长方形的瓷砖,现在的 一块长方形瓷砖相当于原来的两块正方形瓷砖, 工人们拿不定主意该买多少了, 读者朋友们 请帮忙分析一下:能否用 1×2 的瓷砖去覆盖 N×M 的地板呢?
单看这个问题,其实挺简单的,很明显,只要N、M至少有一个能被2整除,便可以使问题成立。
但是在此,书中又提出了一个扩展问题:用 1×2 的瓷砖去覆盖 8×8 的地板,有多少种方式?如果是 N×M 的地板呢?
其实,不难想到,只要解决了 N×M 的地板的一般性问题,前面的 8×8 的地板也就迎刃而解。但是,在此之前,还必须判断原问题(能否用 1×2 的瓷砖去覆盖 N×M 的地板),因为在此前提下,右面的扩展问题才有意义。
那好,下面就讨论扩展问题的解题思路:
其实,对于 N×M 的地板中第row行pos列上的某个1×1的格子而言,在铺瓷砖的时候会有以下3种状态考虑:该格子先空着不放,以备下一行来使用
根据上一行(row-1,pos)位置上是空着的,则竖着铺一个瓷砖,占领(row-1,pos)和(row,pos)位置。
在空间许可的前提下,横着铺一个瓷砖,占领(row,pos)和(row,pos+1)位置。
那么,如何记录瓷砖不同的摆放呢??
我们假定,row行pos列上的格子若被占领则为1,没占领则为0。例如,存在一个矩阵:0110
1001
0110
1111
但是我们不打算构造这样的一个矩阵,而是将每行的0、1二进制值构成的数值作为列值构造矩阵dp[n][2^(m-1)+1]。2^(m-1)可通过1<
但是,如何来求矩阵dp[n][2^(m-1)+1]??
我们再看回上面的01矩阵,每一行可以有很多状态,其对应的数值可以是0~2^(m-1),可能某一行的一个状态可以与上一行的多个状态兼容,例如:
矩阵一:001111矩阵二:001100
110011110011
111111 111111
由上面可以看出,两个矩阵中,第二行的状态同样是110011,但可以分别与上一行的001111和001100兼容,当然,此时的摆放方式已经不一样了。
由上面的例子不难想到,第二行的110011状态对应的铺排方式数目应该是能和其兼容的上一行的所有状态所对应的铺排方式数目之和,所以可以使用累加的方式计算dp,dp[row][state]=dp[row][state] + dp[row-1][x],其中x是指上一行的状态,如001111、001100。
为什么是累加呢??
首先,非常重要的一点是,dp[row][state]含义是第row行的某个状态与第1到row-1行中符合题意的状态的组合数。譬如说,第一行符合题意的状态有状态11、状态12、状态13、状态14、状态15,第二行中与状态11、状态12、状态13兼容的是状态21,第二行中与状态14、状态15兼容的是状态22,而第三行与状态21、状态22兼容的是状态31。用图就可将地板表示为:
状态11 状态12 状态13 状态14 状态15
状态21 状态21 状态21 状态22 状态22
状态31 状态31 状态31 状态31 状态31
初始化:dp[1][状态11]=1,dp[1][状态12]=1,dp[1][状态13]=1,dp[1][状态14]=1,dp[1][状态15]=1,
dp[2][状态21]=0,dp[2][状态22]=0,dp[3][状态31]=0。
计算过程:(累加)
(1)计算dp[2][状态21]
dp[2][状态21] = dp[2][状态21] + dp[1][状态11] = 0+1 = 1,
dp[2][状态21] = dp[2][状态21] + dp[1][状态12] = 1+1 = 2,
dp[2][状态21] = dp[2][状态21] + dp[1][状态13] = 2+1 = 3。
(2)计算dp[2][状态22]
dp[2][状态22] = dp[2][状态22] + dp[1][状态14] = 0+1 = 1,
dp[2][状态22] = dp[2][状态22] + dp[1][状态15] = 1+1 = 2。
(3)计算dp[3][状态31]
dp[3][状态31] = dp[3][状态31] + dp[2][状态22] = 0+3 = 3,
dp[3][状态31] = dp[3][状态31] + dp[2][状态22] = 3+2 = 5。
实际上,dp[3][状态31]的数值就是铺排的方式的数目,正如上面图上的5个地板。
算法过程:
首先初始化第一行,利用之前讨论的3种情况,找出第一行中符合题意的状态。例如,000001就不符合题意,不可能第一行上就只出现一个1。
对于第k行(2<=k<=n),从pos位置上(0<=pos<=M-1),考虑之前讨论的3种情况
(1)该位置写0(意义是空着,以备下一行使用)
(2)如果上一行(第k-1行)该位置上是0,则考虑下一位置(相当于一个子问题);
(3)在pos<=M-2的前提下,将该位置和下一位置即是pos、pos+1上写1,然后再考率pos+2位置;
在考虑以上3个情况之前,首先判断pos是否等于M(等于M说明之前的位置讨论已经到达M-1),这时,该行的状态已经确定,那么就按dp[row][state]=dp[row][state] + dp[row-1][x]求解,其中x是指上一行的状态。
其实,该算法是,通过调整相应pos位置上的01值,考虑当前一行哪个状态可以与上一行的状态兼容,而累计当前一行的可以兼容的状态的dp上。但是可以看出,对于最后一行来说,肯定要全部位置写1才行,因为无论是竖着放、还是横着放,最后一行的位置上必须是1,因为最后一行已经不容许再空着了。所以,最后一行的状态是1111...111,也就是该状态记录了整个地板的dp。
#include
#include
/** n * m 的地板 */
int n,m;
/** dp[i][j] = x 表示使第i 行状态为j 的方法总数为x */
__int64 dp[12][2049];
/* 该方法用于搜索某一行的横向放置瓷砖的状态数,并把这些状态累加上row-1 行的出发状态的方法数
* @name row 行数
* @name state 由上一行决定的这一行必须放置竖向瓷砖的地方,s的二进制表示中的1 就是这些地方
* @name pos 列数
* @name pre_num row-1 行的出发状态为~s 的方法数
*/
void dfs( int row, int state, int pos, __int64 pre_num )
{
/** 到最后一列 */
if( pos == m ){
dp[row][state] += pre_num;
return;
}
/** 该列不放 */
dfs( row, state, pos + 1, pre_num );
/** 该列和下一列放置一块横向的瓷砖 */
if( ( pos <= m-2 ) && !( state & ( 1 << pos ) ) && !( state & ( 1 << ( pos + 1 ) ) ) )
dfs( row, state | ( 1 << pos ) | ( 1 << ( pos + 1 ) ), pos + 2, pre_num );
}
int main()
{
while( scanf("%d%d",&n,&m) && ( n || m ) ){
/** 对较小的数进行状压,已提高效率 */
if( n < m ){
n=n^m;
m=n^m;
n=n^m;
}
memset( dp, 0, sizeof( dp ) );
/** 初始化第一行 */
dfs( 1, 0, 0, 1 );
for( int i = 2; i <= n; i ++ )
for( int j = 0; j < ( 1 << m ); j ++ ){
if( dp[i-1][j] ){
__int64 tmp = dp[i-1][j];
/* 如果i-1行的出发状态某处未放,必然要在i行放一个竖的方块,
* 所以我对上一行状态按位取反之后的状态就是放置了竖方块的状态
*/
dfs( i, ( ~j ) & ( ( 1 << m ) - 1 ), 0, tmp ) ;
}
else continue;
}
/** 注意并不是循环i 输出 dp[n][i]中的最大值 */
printf( "%I64d\n",dp[n][(1<
}
return 0;
}
程序说明
1.初始化第一行,dfs(1,0,0,1),因为第一行没有上一行了,而在累加的时候实际上加的就是符合题意的状态数目。
2.接下来的两层for循环,就是遍历所有行上的所有状态,并根据上一行的情况来考虑,可以看到其代码是
dfs( i, ( ~j ) & ( ( 1 << m ) - 1 ), 0, tmp ) ;
对上一行的状态取反,其实是把上一行为0的位置,也就是空着的位置所对应的下一行的该位置写1,因为该上一行该位置空着是想填一个竖着的瓷砖,所以可以直接确定该位置。而后面与上了( ( 1 << m ) - 1 )是为了j在超过m的位置上都为0。还有dp[][]的某个值为0时,说明该状态不可能出现,所以不用考虑。
3.在dfs函数中,就是根据之前讨论的3种情况来实现。
对与第二部分 dfs( row, state, pos + 1, pre_num ); 其实是该pos位置不管了,原来是1就是1,是0就是0。直接考虑下一位置。为什么这样呢?因为如果该位置是0的话,那就是默认的0,考虑的就是该位置空着的情况,但如果该位置是1的话,那就是上一行该位置是0,已经空着了,而这一行该位置必须为1,在调用之前经过取反操作已经置为1,我们不用管了。
对于第三部分,考虑在改行上横放一个瓷砖
根据之前的讨论,肯定要判断够不够位置横放;其次还要判断pos、pos+1位置上原来是不是1,因为有些位置是因为竖着放的缘故,已在取反操作中写1了。如果不是1就可以横放瓷砖了。
4.因为最后一行所有位置上肯定都是1,所以最后结果在dp[n][(1<
瓷砖覆盖地板c语言程序,瓷砖覆盖地板的扩展问题相关推荐
- 瓷砖覆盖地板c语言程序,编程之美4.2 瓷砖覆盖地板
题目 这个题目的题意很容易理解,在一个N*M的格子里,我们现在有两种类型的 砖块,1 * 2 和 2 * 1,问一共有多少种方案,可以将整个N*M的空间都填满. 最简单的例子就是下面的了: 编程之美中 ...
- c语言程序编写字体,c语言程序设计练习题
c语言程序设计练习题 篇一:c语言程序设计基础单元总结与练习题及答案 <C语言程序设计>单元总结与练习题 答 案 单元一 程序设计宏观认识 单元总结提升 本单元中,核心内容有C语言程序框架 ...
- 覆盖40种语言:谷歌发布多语言、多任务NLP新基准XTREME
关注上方"深度学习技术前沿",选择"星标公众号", 资源干货,第一时间送达! 自然语言权威数据集 GLUE 一直是衡量各机构 NLP 预训练技术水平最重要的指标 ...
- Facebook开源最大规模并行语料,45亿语料,覆盖576种语言对
2020-02-07 16:33 导语:或成为NMT评估标准 雷锋网AI科技评论按:当前自然语言处理中的大多数方法都是数据驱动的,大多数多语言模型(特别是神经机器翻译系统)都需要并行语料库进行训练.大 ...
- 进博会指定传神提供智能翻译硬件,多举措保障语言服务全覆盖
进博会指定传神提供智能翻译硬件 多举措保障语言服务全覆盖 11月5日,一场全球瞩目的贸易盛宴--首届中国国际进口博览会(以下简称"进博会")在上海隆重开幕.对于前来参展的130个国 ...
- python语言程序设计2019版第二章课后答案-python语言程序设计基础课后答案第二章...
python语言程序设计基础课后答案第二章 以下合法的用户自定义标识符是____________. 导入模块或者模块中的元素要使用关键字________ . 下列哪个函数是用来控制画笔的尺寸的____ ...
- c语言程序的入口是哪部分,C语言入口函数和LD_PRELOAD环境变量
零.C语言入口函数 从第一天学习C语言开始,我们的脑子里就深深烙下这样一个概念:C语言程序总是从main()函数开始执行,main()函数结束,程序也就结束了.在平时的练习中貌似这没有问题,但事实真的 ...
- c语言实验分支程序设计二,C语言程序实验报告分支结构的程序设计(0页).doc
C语言程序实验报告分支结构的程序设计(0页) 数学与软件科学学院 实验报告 学期:11至12___ 第_1 学期 2011年10 月 17 日 课程名称:程序设计基础教程-C语言 专业:2010级5_ ...
- 走进C 语言:你知道C语言程序是如何执行的吗?
C 语言程序成为高级语言的原因是它能够读取并理解人们的思想.然而,为了能够在系统中运行 hello.c 程序,则各个 C 语句必须由其他程序转换为一系列低级机器语言指令.这些指令被打包作为可执行对象程 ...
最新文章
- android studio 的自动更新问题
- 边缘计算云原生开源方案选型比较
- PHP-Zend引擎剖析之词法分析(一)
- 一文了解Kubernetes的前世今生
- js rsa解密中文乱码_建议收藏 | 最全的 JS 逆向入门教程合集
- yii2中的事件和行为
- 实例1.2:获得应用程序主窗口指针
- java常用类介绍及源码阅读(LinkedList)
- ASP.NET 2.0中实现模板中的数据绑定
- lnmp环境搭建完全手册(四)——lnmp搭建(源码安装)
- cf706C(dp)
- Python 语言程序设计(2)基本图形绘制
- jquery所有版本下载
- 怎样美化计算机的桌面图标,windows10图标美化怎么操作_win10电脑美化方法
- SpringBoot yml文件命名规则
- 计算机专业新手小白学编程如何选择笔记本电脑
- Matlab建的模型如何导入MS中,lammps输出的模型如何导入MS中建模
- git配置代理 代理 socks5带用户名密码
- 管理者应该怎么面对员工的顶撞
- 拉卡拉支付:推出支付产业互联网新大门