状态压缩方法

>>最近了解了一些题目,其中对棋盘或者是汉诺塔的表示通常都用到了状态压缩的方法,配合BFS或者是DP来作。算法

题目连接:数组

汉诺塔移动 http://stackoverflow.com/questions/16601701/facebook-sample-puzzle-towers-of-hanoi数据结构

黑白棋游戏 http://www.wikioi.com/problem/2743/优化

 

状态压缩是信息学竞赛中一个很常见的方法,最最多见的是二进制压位。

作过USACO的同窗会知道有不少搜索和DP均可以用状态压缩优化。

通常来说,若是状态的够成很是多,但每一个构成相对简单,就能够状态压缩,好比巨大的0/1矩阵等。
固然若是状态压缩,提取每一个数据起来就会耗费更多的时间,因此通常运用在状态复制量较大,比较量较小的状况下。

举例:

多关键字排序能够用状态压缩,如A,B,C三个关键字,均小于100,能够压缩成A*10000+B*100+C,直接比较便可,很是方便。

N皇后的状态压缩,配合位运算,神速。

相邻两行之间的匹配关系,压成二进制,作DP。

其实只要想节约空间均可以用状态压缩。
压缩通式:0<a<A 0<b<B 0<c<C   (关键程度a>=b>=c)  <==> T=a*B*C+b*C+c ,A*B*C<maxstruct
为了方便通常使A=B=C,转换成A进制数便可

(*)状态压缩的效率:spa

常见的把5个小于100的数压成一个longint的数,若是是5个小于50的数呢?

1.空间利用率:显然按十进制压缩的空间效率过低,能够考虑按50为一个单元压缩,
2.时间效率:提取时大量的取模和除法运算使效率过低,能够考虑换成64进制的数,进行压缩。
这里的空间又有了必定程度上的浪费,也体现了时间与空间的辩证关系。设计

 

关于状态压缩动态规划排序

引入 
首先来讲说“状态压缩动态规划”这个名称,顾名思义,状态压缩动态规划这个算法包括两个特色,第一是“状态压缩”,第二是“动态规划”。 

状态压缩: 

从状态压缩的特色来看,这个算法适用的题目符合如下的条件: 
1.解法须要保存必定的状态数据(表示一种状态的一个数据值),每一个状态数据一般状况下是能够经过2进制来表示的。这就要求状态数据的每一个单元只有两种状态,好比说棋盘上的格子,放棋子或者不放,或者是硬币的正反两面。这样用0或者1来表示状态数据的每一个单元,而整个状态数据就是一个一串0和1组成的二进制数。 
2.解法须要将状态数据实现为一个基本数据类型,好比int,long等等,即所谓的状态压缩。状态压缩的目的一方面是缩小了数据存储的空间,另外一方面是在状态对比和状态总体处理时可以提升效率。这样就要求状态数据中的单元个数不能太大,好比用int来表示一个状态的时候,状态的单元个数不能超过32(32位的机器)。 

这里举一个能够状态压缩的例子,好比poj上的第1753题Flip Game(见poj1753解题报告),虽然这道题目不是用动态规划作,可是能够用状态压缩,4×4的格子,每一个格子的状态为黑或者白,这样就能够用一个16位的二进制数来表示,在实现的时候能够用一个int类型来表示这个二进制数。在状态和状态对比和转换的时候能够用位操做来完成。 

位操做实现技巧: 
若是要得到第i位的数据,判断((data&(0X1<<i))==0),若真,为0,假,为1; 
若是要设置第i位为1,data=(data|(0X1<<i)); 
若是要设置第i位为0,data=(data&(~(0X1<<i))); 
若是要将第i位取反,data=(data^(0X1<<i); 
若是要取出一个数的最后一个1(lowbit):(data&(-data))         (这里利用的是负数取反加1实际上改变的是二进制最低位的1这个性质)
递归

 

插一句:负数的补码表示:(负数的补码等于对应的正数二进制表示取反加1)游戏

在从4中减去12时,究竟进行了什么操做?其实是对负二进制数值采用了2的补码形式。这里须要作一个约定,以免解释它为何有效。下面看看如何从正数中构建负数的2的补码形式,读者也能够本身证实这是有效的。如今回到前面的例子,给-8构建2的补码形式。首先把+8转换为二进制: 
0000 1000 
如今反转每一个二进制数字,即把0变成1,把1变成0: 1111 0111 
这称为1的补码形式,若是给这个数加上1,就获得了2的补码形式: 1111 1000 
这就是从+4中减去+12,获得的–8的二进制表示。为了确保正确,下面对–8和+12进行正常的相加操做: 
+12转换为二进制 0000 1100 –8转换为二进制 1111 1000 
把这两个数加在一块儿,获得: 0000 0100ip



动态规划: 
若是说状态压缩是数据结构的话,那么动态规划应该是算法了。题目经过动态规划来解一般有两个动机,第一是利用递归的重叠子问题,进行记忆话求解,即递归法的优化。第二是把问题看做多阶段决策的过程来求解问题。在状态压缩动态规划中咱们讨论的是第二种动机。 

多阶段决策过程求解问题的动态规划最重要的是划分阶段和找到状态转移方程。对于划分阶段,是根据不一样阶段之间的独立性来划分,一般会用状态数组的第一个下标来记录这个阶段的标记(好比01背包问题中的状态数组第一个下标为物品的个数,棋盘放棋子问题中的状态数组的第一个下标为棋盘的行数等等)。另外一个重要的即是状态转移方程,状态转移方程是递推时获得一个状态数据的重要根据。一般状况下状态数组的除了第一个下标之外都是表示状态数据的,而状态数组的值是和所求结果紧密结合的。在后面的几个例题中会重点说明状态转移方程。 

当状态压缩和动态规划结合的时候便造成了一类问题的一种算法,即状态压缩动态规划的算法。这种算法最多见在棋盘问题上或者网格问题上,由于这一类问题的状态数据的单元较少,能够经过状态压缩来对当前棋盘或者网格的状态进行处理。 

例题解析 
例:在n*n(n≤20)的方格棋盘上放置n 个车,某些格子不能放,求使它们不能互相攻击的方案总数。 

分析: 
首先看到这道题目,咱们可能会想到8皇后问题,用深度优先搜索。可是这里放这道题目是用来讲明此题能够状态压缩动态规划,并且状态压缩动态规划相比于深度优先搜索能够应对更多的变化状况和拥有更高的效率。 

用状态压缩动态规划算法 
1.划分阶段,本题比较简单,以行来划分阶段,即一行一行的放车。 
2.找状态转移方程,由于前i行的状态是根据前i-1行的状态来肯定的,因此,在状态数组中要记录多行状态。故设计状态数组为f[i][x],i表示这是前i行的状态,x在这里是就是一个压缩的状态,记录一个二进制数从而来表示前i行的状态,f的值记录造成前i行的x状态有多少种方案。状态转移方程是f[i][a]=SUM{f[i-1][b]},下面讨论a状态与b状态的关系。先举个例子,若是a状态为01011,代表前i行中的第0,1,3列都放置了一个车(从右往左看)。因而b的状态就多是01010,01001,00011三种状态,因而f[3][01011]=f[2][01010]+f[2][01001]+f[2][00011]。再看广泛的状况,a-b的值的二进制表示中只有一个1。并且要保证a状态,b状态不能和非可用的格子冲突。综上,状态转移方程为: 
f[i][a]=SUM{f[i-1][b]} (a-b的值的二进制表示中只有一个1; a,b不与题目中的约束条件冲突) 
这样经过递推能够获得f[n][1…1]的方案数即为最后的方案总数。 


算法总结: 
1.判定这道题目能够用数据压缩动态规划来作。重点是看它的特色,是否符合状态压缩动态规划的条件。 
2.划分阶段。像棋盘和网格问题大多数是一行一行的进行操做,故以行来划分阶段,固然也有其余的阶段划分方法,具体问题具体分析。 
3.找状态转移方程 
3.1设计状态数组。一般数组的第一个参数为阶段的标志,其余几个参数为记录状态用(若是第i行的状态能够经过第i-1行的状态来肯定,则须要一个参数,即第i行的状态;若是像炮兵阵地那题同样,第i行的状态须要根据第i-1行和第i-2行来肯定,则须要两个参数,分别为第i行的状态和第i-1行的状态,具体见附录)。数组的值与结果挂钩,一般有如下几种状况:a.题目要求一共有多少的方案,这时数组的值为当前状态的方案数。(好比poj2411以及例题)b.题目要求最佳方案,这是数组的值为最佳方案的值。(好比poj1185炮兵阵地)。 
3.2列出状态转移方程,对应与上面的a,b两种状况,a状况时,状态转移方程为f[i][a]=sum{f[i-1][b]}; b状况时,状态转移方程为f[i][a]=max{f[i][b]}+sth. 
3.3找出状态转移方程中a,b之间的关系。即为状态转移方程添加约束条件。 
4.在不少状况下须要对每个阶段的可能值进行dfs来找出全部的可能值存储起来,以便在对每个阶段处理的时候可以很快的运用以提升效率。

相关文章
相关标签/搜索