6.状态压缩DP
将一个十进制的整数转化为二进制数,每一位表示一种状态
AcWing 291. 蒙德里安的梦想
实现思路:先放横着的,再放竖着的。
总方案数,等于只放横着的小方块的合法方案数。(放完横着的方块之后,竖着的只能被动填充进去,不需要额外进行竖着的情况)
方案合法的条件是:当横着的方块放完后,竖着的小方块恰好能把剩余的地方全部填满。
那如何判断方案是否合法呢?即怎么看竖着的小方块是否能把剩余部分填满呢?因为是竖着放的,所以可以按列来看,每一列的内部,只要所有连续的空余小方块的个数为偶数,即可。
状态表示:
f[i][j]
表示前i-1
列已经摆好,且从第i-1
列是否有伸出一格到第i
列的情况(这个情况用j
表示)的所有方案数。对于
j
:j是一个二进制数,位数与棋盘的行数相等,比如N=5,M=5的棋盘,j为5位的二进制数(XXXXX)_2,但在程序中还是用十进制数表示。(所以要用位运算来判断某一位是1或0)什么叫
j
表示从第i-1
列伸出一格到第i
列的情况?如下图,第i
列的第1,2,5
个格子,是从i-1
列伸过来的。此时的状态j
为 (11001)_2,即对于第i
列的所有格子,第1,2,5
个格子被伸出来占据了(j
是个二进制数,若该列的某一行,有被前面的列伸出来一格,则用1
表示,否则用0
表示),那么这些个位置被侵占了,就不能再横放了。这样对
f[i][j]
更通俗的理解就是第i
列的横放情况,由j
的二进制表示出第i
列哪些行可以横放
状态转移:既然第
i
列固定了,我们需要看 第i-2
列是怎么转移到到第i-1
列的(看最后转移过来的状态)。假设此时对应的状态是k
(第i-2
列到第i-1
列伸出来的二进制数,比如00100),k
也是一个二进制数,1表示哪几行小方块是横着伸出来的,0表示哪几行不是横着伸出来的。它对应的方案数是
f[i−1,k]
,即前i-2
列都已摆完,且从第i-2
列伸到第i-1
列的状态为k
的所有方案数。那
k
需要满足什么条件,才能够从f[i - 1][k]
转移到f[i][j]
呢?①k和j不能冲突,也就是第
i-1
列和第i
列的不能有重叠的1,即两列的相同行不能同时为1(不能同时有前一列伸过来的)。如下图,在第一行k的二进制位为1,j的二进制位也为1,那么第i列就不能再横放东西了。转化为代码判断就是**(k & j ) ==0
** ,表示两个数相与,如果两有对应位同时为1,则相与结果为1,否则为0即没有冲突。②既然从第
i-1
列到第i列横着摆的,和第i-2
列到第i-1
列横着摆的都确定了,那么第i-1
列 空着的格子就确定了,这些空着的格子将来用作竖着放。如果 某一列有这些空着的位置,那么该列所有连续的空着的位置长度必须是偶数(即不能有奇数个0)。设置一个st[]数组表示的是这一列连续0的个数的情况,若为true表示连续偶数个0(合法状态),否则为false。体现到代码判断第i-1
列满足竖放的合法状态:**st[k|j]=true
**,解释:第i-1列中1的总个数,就是当前 第i-1列的到底有几个1,即哪几行是横着放格子的
st[k]表明第i-2列插入到i-1列的1的个数,i-1列被动生成的1
st[j]表明第i-1列插入到i列的1的个数,也就是i-1列自己生成的1
最后结果输出
f[m][0]
注:对于f[0][0]=1
和最后输出f[m][0]
理解:数组下标从0开始表示实际棋盘的第一列,放f[][]
表示当前列的横放情况,f[0][0]
表示当前一列没有伸过来的(因为前面根本没有列),所以也就没有横放的情况,只有竖放的情况,即f[0][0]=1
。所以输出f[m][0]
表示第m列不横放,这也是合理的,如果第m列横放了,那就超出m列的范围了。
其他理解:AcWing 291. 蒙德里安的梦想 - AcWing
1 |
|
AcWing 91. 最短Hamilton路径
f(i,j)
表示从0号点到j
号点,且中间经过点的状态是i
(和上题类似,用二进制表示,位为1就表示某点经过,同样要进行位运算来获得每一位。如 1 1 1 0 1 表示第0,1,2,4被走过)的最短路径长度。- 集合划分:最后一个点是
j
,按倒数第二个点划分,若倒数第二个点是k
,即从0号点经过一系列不重复的点到达点k
,再经过点k
到达终点j
。同时在这个状态i中就不能再包括点j了(因为路径要不重不漏),要再状态i中除去j。 - 最终状态转移方程为:**
f[i][j]=min(f[i][j],f[i-1<<j][k]+a[k][j])
;**因为i
是二进制数,除去j
点就表示使j
位置上的二进制位由0后改为1,即相减。
1 |
|