声明:此为转载,原文链接:BUCT OJ 网友chdh14b 另外此博客有详细完整代码:http://blog.csdn.net/math_coder/article/details/9671581 大家好~我和大家分享一下我对状态压缩类的动态规划题的一些看法。 顾名思义,状态压缩类的动态规划的本质是动态规划,所以要处理好状态压缩问题首先要把握好动态规划的解题思路。 首先,来回顾一下动态规划算法的设计: (1)首先把握问题的解是否可以分解的,可分解则考虑是否可用动态规划算法。 (2)描述最优解的结构。 (3)递归定义最优解的值。 (4)自底向上的方式计算最优解的值。 (5)有计算出的结果构造出一个最优解。 然后,我们来看一看状态压缩类的动态规划算法的设计和普通动态规划算法的区别: (1)和普通的dp一样,要把握问题的解是否可以分解。同时,解是一些状态的集合或一些特定的状态。如:n个车放在棋盘上的某些位置上,就是一个状态;在一张图上,选择走某条线路,通过某些点, 也称作一种“状态”;在牧场上,某些点放牧某些点不放,也是一种状态。当然,状态需能分解成几个“小”状态。 如:当把n个车摆放的位置称作总状态的话,某一个车的位置可以称作一个“小”状态,某一行上的所有车的位置也可以称作一个“小”状态。具体根据题目的问题而定。 (2)把握如何描述一个状态。首先,用二进制的0和1来描述最小的状态。如:在棋盘上的一个点,放祺和不放祺可以表示为1和0;图上,走某条线某个点或不走可表示为1和0。 简而言之,To be or not to be, that is just one or zero。而后,一个比最小状态稍大的状态,就可以用一连串的0和1来表示。如:棋盘的某一行(1~5)在1、3、5号位置摆放棋子可以表示为 10101。 然后,把状态压缩,降低存储成本(空间开销)。状态压缩在动态规划里较为特殊的原因之一就是因为一个“状态压缩技术”,或者叫做压缩小技巧,就是将二进制数表示的状态用整形量来存储。 比如,二进制数10101可以用整型量21来表示。 (3)写出状态转移方程。这个要根据具体问题具体分析。 (4)确定边界条件,然后自底向上计算最优状态解的值(特定状态的属性?,etc.)或者所有状态解的和的值(状态集合的状态总和,etc.)。 (5)对于状态压缩类的动态规划问题,一般不会要求求出最优解。所以不需要构造最优解,只需求出最优状态解的值或所有状态解的和。 由此可见,状态压缩类的动态规划算法仅仅是动态规划的一种特例,一种对特殊问题的特殊求解方式。 接下来,我会分析buct_oj上动态规划(一)的ProblemE-ProblemI: (以下代码并非完整的AC代码,而是我对AC代码的抽象出来的半伪代码。所以边界条件从0还是从1之类的并不强调,只为说明我个人对状态压缩算法的看法) (一)问题 E: 状态压缩DP----车的摆放1 题目来源:http://coder.buct.edu.cn/JudgeOnline/problem.php?cid=1032&pid=4
题目描述
在n*n(n≤20)的方格棋盘上放置n 个车(可以攻击所在行、列),求使它们不能互相攻击的方案总数。(状态压缩DP的入门题,希望大家不要使用组合数学公式求解)
个人分析 (1)解可否分解,解是否“状态化”的:显然,解是“状态的集合”。某一总状态为棋盘上满足题目条件下n个车放在棋盘上的状态。该总状态可以分解为“小状态”。 (2)对于这个问题,我将第i行的“小状态”定义为:第一行到第i行的所有棋子状态。用一串二进制数表示,某列的1表示从第一行到第i行,该列上有一棋子。0表示,无棋子。 即如,r*c = 4*4的棋盘上(如图一),i = 3时,状态(state)为1101。然后将1101压缩成整型数13。 用num[i,State]表示第i行在state的“小状态”下的方案数。 (3)状态转移方程:显然,第i行State状态下的num[i,State]与第i-1行的某些状态数有关。 不难推出:num[i,State|PreviousState] = sum{num[i-1,PreviousState] | State&PreviousState == 0} 这里用到了简单的对二进制数的处理。即取与为0即表示列上没有棋子冲突。还有一点,因为一行只能有一个棋子,这里的state只需取特殊的一些串二进制数。这串二进制数里只有一个1,其余全为0。 即0001,0010,0100,1000。可以再将效率提升,用一个一维长度的数组把这几个数存起来,每次只需取出其中一个进行计算即可。抑或可以用左移运算,1<<j(j从0~r-1)来表示。(这里效率不变) (4)自底向上运算,第一行num[1,State]显然为1。只需将第一行特殊处理,再把num从1~r行的值都算出来即可。所有状态解的和的值即第r行状态为1111...r个1时的num[r,(1<<r)-1]的值。 以下为最简单的计算代码: (我的最初的代码还有一个函数用作判断某二进制数中1的个数是否符合该行的要求。如:第r行的状态必有且只有r个1,其余为0。 在此我将其删去,因为我个人觉得这么运算下来状态不符合r行有r个1的num为0。所以不影响运算。)
- memset(num,0,sizeof(num)); // Initialize array.
- for (int State = 1; State <= r; State = State << 1) {
- // Represent 00..01, 00..10, ..., 01..00, 10..00.
- num[1,State] = 1;
- }
- for (int i = 1; i <= r; i++) {
- // i is the index of row.
- for (int State = 1; State <= r; State = State << 1) {
- // Represent 00..01, 00..10, ..., 01..00, 10..00.
- for (int PreviousState = 1; PreviousState <= (1<<r); PreviousState++) {
- // Represent all combination of zero and one.
- if ((State&PreviousState) == 0)
- num[i,State|PreviousState] += num[i-1,PreviousState];
- }
- }
- }
复制代码
此外,还可以优化一下效率,用一个数组StateArray保存State的所有可能状态。不过这道题的作用不大。用于后面的题作用较大。
- memset(num,0,sizeof(num)); // Initialize array.
- for (int State = 1,int s = 1; State <= r; State = State << 1) {
- // Represent 00..01, 00..10, ..., 01..00, 10..00.
- num[1,State] = 1;
- // Storage for valid combination of 0 and 1 to state.
- StateArray[s++] = State;
- }
- for (int i = 1; i <= r; i++) {
- // i is the index of row.
- for (int s = 1; s <= r; s++) {
- // StateArray[s] stores State.
- for (int PreviousState = 1; PreviousState <= r; PreviousState++) {
- // Represent all combination of zero and one.
- if ((StateArray[s]&PreviousState) == 0)
- num[i,StateArray[s]|PreviousState] += num[i-1,PreviousState];
- }
- }
- }
复制代码
这里还可以用滚动数组优化,进一步压缩存储空间。这里只给出部分伪代码,具体请读者自行理解。
- memset(num,0,sizeof(num)); // Initialize array.
- for (int State = 1,int s = 1; State <= r; State = State << 1) {
- // Represent 00..01, 00..10, ..., 01..00, 10..00.
- num[State] = 1;
- // Storage for valid combination of 0 and 1 to state.
- StateArray[s++] = State;
- }
- for (int i = 1; i <= r; i++) {
- // i is the index of row.
- for (int s = 1; s <= r; s++) {
- // StateArray[s] stores State.
- for (Previous is a state which just has i of the one) {
- if ((StateArray[s]&PreviousState) == 0)
- num[StateArray[s]|PreviousState] += num[PreviousState];
- }
- }
- }
复制代码
最后将r个1的num输出即可。 |