状态压缩型动态规划是一种实用的算法。顾名思义,它的优点就在于能够将一些可以表示为非 0 即 1 的状态进行压缩,从而大大节省了空间。说白了,状压 DP 就是利用了“位”的关系。因此位运算和集合的一些相关内容是学习状压 DP 必不可少的预备知识。
先来说集合。集合其实可以简单地看作一些数无序地集在一起,而且一般情况下集合中没有重复的元素。
举个例子吧。我们现在有集合
S={1,2,3,5,6}
。
那么就有一个“子集”的概念。子集是指选取原集合的某一些元素所组成的一个新集合,例如
{1,6,5}
就是
S
的一个子集,同样
在状态压缩型动态规划中,状态往往是可以表示成一个集合的。选了一些元素,其他的一些没选。而转移的时候,正是由子集转移到原集,因此,如何恰当地表示集合、枚举子集就成了关键。
当然,我们可以用数组来描述集合;但是这样一来,一些基本操作的时间都是线性的,常数已经很大了,对我们不利。
仔细想想,其实选或不选不就是0或1的两种情况么。不就可以表示成
例如
{1,2,4,5,6}
,我们可以表示为111011(高位在前!),对应的10进制就是59。
也就是说,有一个数
S
,虽然储存的时候是十进制,但是 C++ 已经有一些基本的自带位运算,例如并、或、异或、取反等,这些其实都可以对应集合的运算。
这里我们先讲讲子集判断的问题。现在我们有用数表示的集合
一,根据子集的定义,
y
中的每个元素都会在
二,既然子集属于原集的一部分,不应该有多出来的元素,也就是说,在对应位置上,如果 x 是 0,那么
要注意一个细节,在 C++ 中位运算符的优先级是要比大于号、小于号、等于号、大于等于、小于等于这些低的,所以判断的时候必须加括号。
原理并不难理解,但状态压缩型动态规划也是属于动态规划的范畴,最重要的还是找出子问题。
例题:原子弹
另外,int 最多表示32位,long long 最多表示64位,其实 C++ 的 STL 中还有一个 bitset,可以支持比较大的二进制集合运算。