状态压缩动态规划——以多米诺骨牌铺设为例

首先我们看一下描述:


情况描述

这里有张图配合描述:


图1,图例

这里用到了状态压缩动态规划,分析一下这个算法过程。也就是步骤化。

本算法中需要定义的几个概念

1,每行状态(state)

用一个二进制的数字表示,如果某位为0,则意味着该行未被占用,如果为1,则表示已被占用。例如(011000),则表示有4个空位。

图2,一个二进制表示棋盘某一行状态的例子

2,可用状态枚举(usableEnum)

对每行按位状态取反(~),同时依次去掉每一个的一个1,直至到全为0的情况,则得到一个可用状态的枚举。例如可用状态为10001,则可用状态枚举为01110,01100,01010,00110,01000,00010,00100,00000。这里需要用到一个位运算的公式,即

for(x=s;;x=s&(x-1)){

if(x=0) break;

}

在这里顺便对这个公式进行证明:

命题:for(x=s;;x=s&(x-1))可以逐步去掉s中的1,得到可用状态枚举.

运用数学归纳法,假设原二进制串为s

第一次进行操作时,x=s,x=s&(x-1),得倒的x就是去掉最靠右的那个1,假设成立。

若进行第n次操作后,得到的x是去掉了原二进制串s中的k个1之后的串s'

那么对于第n+1次操作,用s'-1得到的串s'',具有一个性质:即s''等于s'最右的一个1变成0,然后其靠右的所有0皆为1.此时s&s''则会将刚才s'中最靠右的那个1变成0,其他位保持不变。假设成立。

这里有点复杂,需要配图讲解。

3,摆放策略(tactic)

针对当前处理的一行,我们会有一系列的骨牌摆放策略,注意我们并不能事先知道当前策略能否使最终结果最大,所以我们需要迭代符合当前行以及上一行的所有摆放策略,从中得倒一个最佳策略并存储。这里摆放策略的集合是根据可用状态枚举(usableEnum)上一行的损坏情况进行某种位运算操作得倒的。


算法的步骤分解

在定义完以上三个概念后,我们就能用动态规划的思路来尝试解决问题,如果对上述三个概念还不是特别理解,请耐心继续往下看,本文会通过逐步分解的方式把算法思路讲清楚,在这个分解过程中,自然就能理解为什么需要以上三个概念。

图3,手绘某棋盘的情况

从上图中我们可以看到,一个棋盘中所有格子的占用情况。在这里我们决定从下往上(即从最后一行开始)的求解我们的骨牌摆放策略,实际上你也可以从第一行开始往下进行求解,思路是一样的。

根据动态规划的思想,我们需要将原复杂问题逐步分解成许多较为简单的小问题,而每一个小问题的解决依赖之前已经解决问题的答案,这是动态规划算法的核心思路。

所以首先我们假设,当我们处理到第5行的时候,第6行一直到最后一行的所有可行的最大骨牌策略我们都已经知道,那么我们就可以利用这些已知的信息来求解第5行到最后一行的最大骨牌策略。

首先我们已知第五行的改行状态为:000100,这表示第五行有一个格子是损坏的,而其他格子均可摆放多米诺骨牌。这个时候我们就会自然而然去考虑我们该如何在第五行摆放我们的骨牌,是横着摆还是竖着摆?横着摆几个?竖着摆几个?横着摆还好,因为不会影响其他行,竖着摆说不定下一行对应的位置是个损坏格或者被其他骨牌占用了,根本摆不了。

实际上这些问题都不是问题,注意我们现在假设了我们已经知道了第六行到最后一行所有的占用状态的最大摆放块数,所以我们决定按照以下思路来进行计算。

首先我们根据第五行的状态state:000100,求出第五行的可用状态枚举,求出的公式之前讲过了,这里直接给出答案:{111011,111010,111001,110011,101011,011011,011010,011001,010011,001011,001010,001001,000011,000010,000001,000000}

这里应该对可用状态枚举usableEnum有了一个概念,即当前行所有可行的格子占用方案,注意我这里采用的描述“格子占用方案”,而不是“骨牌铺设方案”,这两者很容易混淆。“格子占用方案”是指,我只知道这个一行的这几个格子是铺了多米诺骨牌的,但是具体是横着铺的还是竖着铺的我并不关心,也就是关注点在格子本身的占用情况上。而“骨牌铺设方案”是指,这一行我的骨牌哪几张是竖着铺,哪几张是横着铺的,关注点在骨牌的摆放位置以及方向上。

在理解了可用状态枚举的概念后,自然会产生一个问题,我们已知了第五行的可用状态枚举后,在这些格子占用方案中,到底哪一种方案会使得从第五行到最后一行能够铺设得多米诺骨牌数量最多?这里你可能又有点迷惑,为什么是第五行到最后一行?时刻记住我们在进行动态规划,我们正是要通过计算出第五行到最后一行的最优解,提供给第四行,从而让第四行能计算出从第四行到最后一行的最优解,这样一直迭代到第一行,那么问题的最终解就得出了。

回到问题本身,显然在第五行的这么多格子占用方案中,我们是无法知道哪个会是最优的,所以我们的思路很暴力,那就是一个个的试,从里面找到最大的

这种一个一个试的方式看上去很笨拙,但是如果没有一些限定条件你也无法获取最优解,也就是我们该如何判断A方案优于B方案?这种找到判断方案优劣条件的过程,在动态规划的术语中叫“状态转移方程”。

接下来我们来举例判断一下,我们从第五行的可用状态枚举中选两个方案来比较一下优劣,比如111010和000011这两个格子占用方案。(一定要理解这个可用状态枚举和每行状态state的区别,他们都是二进制表示,但是意义是完全不同的,在state中,1表示格子不可用,0表示格子可用,而在usableEnum中,1表示此处能放(具体横着放竖着放不关心)骨牌,0表示此处不能放骨牌,这两个概念如果混淆了你将什么也看不懂)。

首先第五行如果是111010这样一个格子占用方案,这意味着我们在第五行最多能放4块骨牌。也就是那四个1的位置我们全部竖着放骨牌,当然我们可能也放不了4块,因为可能第六行对应的位置是个损坏格,导致我们竖着放根本就放不进去。我知道有部分人可能会问,那我竖着放会不会占用了第四行的格子呢?还是再提醒一次,在动态规划看来,现在第四行就是不存在的,也就是在处理第五行的时候他只会去占用第六行的格子。这里好好在体会一下。

于是我们用这样的逻辑计算第五行采用111010这个格子占用方案的最优解:

1,获取第六行的state,从图3中可以知道,是001001.

2,对第六行状态按位取反,~001001 = 110110.

3,用111010 & 110110(做位与运算),得倒110010.,令其为S

在继续后续步骤之前,我先解释一下以上三个步骤的意义。第一步不用多说,第二步对第6行的状态取反,得到了第6行的可铺设格子方案。类比可用状态枚举感受一下,这一步的操作是为了便于第三步的位与运算。第三步将第五行当前选择的格子铺设方案和第六行的可铺设格子方案做与运算,可以得倒一个实际可行的方案,这个方案告诉我们,原本我们以为第五行最多可以竖着放4块骨牌,但是实际上只能竖着放3块,因为第6行中有一个损坏格子给顶死了,导致第五行对应位置想竖着放骨牌的时候塞不进去。

以上三步骤就是为了得倒一个实际可行的方案,不明的反复仔细看一下。

4,统计S中1的个数,也就是竖着放的骨牌个数。用变量stand表示。

5,对S取反,然后和第五行的格子占用方案111010做&运算。也就是001101&111010 = 001000,统计001000中1有几对,即为横着放的骨牌个数,用变量lay表示。这里为什么能够通过这种位运算得到横着放的骨牌个数我到时候补充解释,这里解释起来有点麻烦。暂时先记住这个方法。

6,用S和第六行的状态state做或运算,即 S | 001001 = 111011,也就是把第五行竖着放的格子占用的第六行的格子计算上,得到了当第5行采用S这个方案时,第6行的格子占用状态。由于我们假设我们已知第6行的各种占用方案的最优解,且存到了一个名为dp数组中,这个数组是个二维数组,这个数组中存储着已知的各种格子占用方案的最优解,横轴是行数,纵轴是改行的格子占用状态,比如dp[6][111011]中存储的值就是当第6行的格子占用情况是111011,第6行到最后一行的最大骨牌摆放数量。

7,我们将stand、lay、dp[6][111011]这三个值相加,当第五行竖着摆三个骨牌,横着摆0个骨牌时,第五行到最后一行的最大骨牌摆放数量,将这个数和max比较(这个max值初始为0),如果该数大于max则更新max,反之什么也不做。

8,去掉S中的一个1(用概念2中那个去1公式),回到第4步骤。直到S = 0。

9,当步骤4到步骤8循环完毕后,我们会得到当第五行格子占用方案为111010时的最大骨牌块数max,将其写入二维数组dp[5][111010]中。

然后我们再将第5行状态设置为000011,重新走上面9个步骤,就会得到我们会得到当第五行格子占用方案为000011时的最大骨牌块数max,将其写入dp[5][000011]中。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,937评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,503评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,712评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,668评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,677评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,601评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,975评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,637评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,881评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,621评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,710评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,387评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,971评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,947评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,189评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,805评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,449评论 2 342