动态规划专题讲座
-------
状态压缩型动态规划
所谓状态压缩的动态规划是指状态用一种压缩的形式存储。
一般采用一个二
进制数表示状态,
有时也用三进制或四进制数等。
用二进制数表示状态最大的好
处就是在决策转移时可以采用位运算,这样能极大提高算法效率。
下面我们来看几个有关状态压缩动态规划的问题。
【例
1
】广场铺砖问题
一有一个
W
行
H
列的广场,需要用
1*2
小砖铺盖,小砖之间互相不能重叠,
问有多少种不同的铺法?
输入数据:
只有一行
2
个整数,分别为
W
和
H
,(
1<=W
,
H<=11)
输出数据:
只有
1
个整数,为所有的铺法数。
样例:
Floor.in
2 4
Floor.out
5
样例铺法如下图:
分析:
该题给出的广场的面积很小
,
很容易想到采用搜索的方法,
可以采用深搜或宽
搜均可。
尽管
w,h<=11
,不很大,但是用
1*2
的砖铺,深度最大可达到
11
,这样,如
果采用深搜,对于每一层都需要回溯,时间复杂度也很高。
如果采用宽搜,每一个点都有
2
种铺法,因此可以扩展出
2
个结点,要求所
有的解,必须扩展全部树结构,如果
11
层结点,是个完全二叉树,结点数量可
达
2
11*11
=2
121
,无论空间和时间都难以承受。
因此我们需要采用其他方法。
性质
1
:如果
w
和
h
都是奇数,则无解,否则有解。
证明:
w,h
都是奇数,则
w*h
也是奇数,由于
1×
2
的砖有
2
块,因此无论铺
多少块都是偶数,
因此不能覆盖所有的地板。
如果地板的面积
S
是偶数,
肯定能
被
2
整除,因此可以用
S/2
块砖铺满整个地板。
性质
2
:对于每铺一次地板,只会影响所铺的上下两行,如果按行铺地板,
每一行的铺法都类似。
证明:因为是
1×
2
的砖铺,性质显然。
为了了解得更加清楚,首先我们分析一个
6×
9
的面积铺法,如下图:
可以看出,在按行铺的过程中,某些砖会分成两半,如图
2
,但最多也是向
下突出一格,在图
3
中,我们将图
2
的空隙填满,则又转移到了下一种状态。
如果我们把行进行动态规划,则第
i
行的各种情况即表示第
i
个阶段的的各
种状态。
若某格被铺了砖,则用
1
表示,没有铺砖,则用
0
表示,那么行的状态就是
一个
w
个格子的
0,1
串,即
w
位的二进制数。如下图状态为:
100100
对于每一行的空格,有两种铺法,横铺和直铺,如下图。
因此状态的转移就是由某个二进制数能转化到另一个二进制数的问题了。
如
上图
,
状态由
100100
111100
和
110100
显然,对于每一行
,
宽度为
w,
每格可放
0
和
1
,因此有
2
w
种状态。
设
f(i,s)
表
示铺到第
i
行,状态为
s
的方案数,则
初始值
f(1,0)=1
Ans=f[h+1][0]
时间复杂度为
O(h*2
W
)
。
下面来讲讲位操作:位操作是一种速度非常快的基本运算。有左移、右移、
与、或、非等运算。
左移:
左移一位,相当于某数乘以
2
,比如
110
左移
1
位,变为
1100
,由
6
变为
12
,表示为
(110<<1)=1100
。因此左移
x
位,相当于该数乘以
2
x
右移:
右移一位,相当于某数除以
2
,比如
110
右移
1
位,变为
11
,由
6
变
为
3
,表示为
(110>>1)=11
。因此右移
x
位,相当于该数除以
2
x
s
s
s
i
f
s
i
f
的状态能变到
其中
'
),
'
,
1
(
)
,
(
与运算:
按位进行
“与”
运算,
都为
1
时结果为
1
,
否则为
0
。
例如
101&110=100
或运算:
按位进行
“或”
运算,
都为
0
时结果为
0
,
否则为
1
。
例如
101|110=111
非运算:
每位取反。例如
~101=010
有了上面的知识,下面,来讲一下与该题有关的位操作。
一、若当前状态为
s,
对
s
有下列操作:
1.
判断第
i
位是否为
0:
(S & 1 << i) == 0
,意思是将
1
左移
i
位与
s
进行与运算后,看结果是否为
0.
2.
将第
i
位置
1:
S | 1 << i
,意思是将
1
左移动
i
位与
s
进行或运算
.
3.
将第
i
位置
0:
S & ~ (1 << i)
,意思是
s
与第
i
位为
0
,其余位为
1
的数进行与运算。
例如:
s=1010101
,
i=5
S & 1 << i:
1010101 & 0100000 = 0
S | 1 << i:
1010101 | 0100000 =
1110101
S & ~(1 << i):1010101 & 1011111 = 1010101
二、放置操作
对于每一行有
w
个位置,所以每一行都有
0
~
2
w
-1
种状态。
对于当前行的状态
s
,它是由前一行的状态
s’
转化过来的,显然,对于该行
某个位置
j
:
如果前一行该位置为
0,
那么该位置可以直放
即
0-> 1
如果前一行连续两个位置为
0,
那么这两个连续位置可以横放
即
00-> 00
如果前一行该位置为
1,
显然该位置不能再放
,
于是应该把该位置设置为
0
,
即
1-> 0
如下图:
三、程序框架如下
void dfs(int i, int s1, int s2, int d)
{
if (s == m)
//
如第
i
行已经做完,则累加
f[i + 1][s2] += f[i][s1];
else
if ((jj & (1 << d)) == 0)
//
第
d
位为
0
{
dfs(i,s1, s2| (1 << d), d + 1);
//
将第
d
位变为
1
,并右移
1
位,直放
if (s2 < m - 1 && (jj & (1 << (d + 1))) == 0)
//
如果第
d+1
位也为
0
dfs(i,s1, s2, d + 2);
//
直接搜索
d+2
位
,横放
}
else
dfs(i, s1,s2 & ~(1 << d), d + 1);
//
将第
d
位变为
0,
并右移
1
位,不放
}
下面是
W=4
,
h=2
的求解过程
【例
2
】
硬木地板
举行计算机科学家盛宴的大厅的地板为
M
×
N (1<=M<=9, 1<=N<=9)
的矩形。现在必须
要铺上硬木地板砖。可以使用的地板砖形状有两种:
1) 2
×
1
的矩形砖
2) 2
×
2
中去掉一个
1
×
1
的角形砖
你需要计算用这些砖铺满地板共有多少种不同的方案。
注意:必须盖满,地板砖数量足够多,不能存在同时被多个板砖覆盖的部分。
输入数据
包含
M
和
N
。
输出数据
输出方案总数,如果不可能那么输出
0
。
样例
输入
:floor.in
2 3
输出
:floor.out
5
【分析】
该题与例
1
非常类似,只不过是多了一种砖覆盖,因此思考的方式也与例
1
类似。
先看看2×3的地板所有铺法共5种,如下图:
将矩阵的1行看成一种状态,则某一行矩阵的铺砖情况可以用一个01串表
示:0表示未铺砖,1表示已铺了砖。
由于该题有两种类型的砖,因此对应6种铺法:
同时,对于上下两行:要能用某种类型的砖铺,必须该砖所覆盖的区域为空。
采用位操作对应某种铺法:
#define bin(i) (1 << i)
/*1
左移
i
位
*/
#define emp(a,i) (!(a & bin(i)))
/*
判断
a
的
i
位是否位
0*/
a= a | bin(i) | bin(i + 1); b=b
a =a | bin(i) | bin(i + 1); b= b | bin(i)
a =a | bin(i) | bin(i + 1); b= b | bin(i+1)
a= a | bin(i)
b= b | bin(i)
a= a | bin(i) b= b | bin(i) | bin(i - 1)
a= a | bin(i) b= b | bin(i) | bin(i + 1)
设
f(i,s)
表示第
i
行的状态为
s
时可达的方案数,则: