[算法复习]状压DP 更新中
emmm突然想来讲讲状压DP,这次先简单讲讲状压DP是什么和枚举子集的方法吧。
有时间的话会做点题整理一下放上来。
状压DP
状态压缩
大概是这样,比如你有n个物品,可以取也可以不取,那么我们用1代表取了,0代表枚举,排成一列长度为n的数列,就可以代表n个物品的取舍状况。
然后我们将这个数列视为一个二进制串,就得到了一个状态,比如11101。这就是状态压缩。
一般来说对于一个状态\(f[11101_{(2)}]\)即\(f[29_{(10)}]\),我们可以从它的子集转移到它。
所以我们下面来讨论如何枚举子集
子集枚举
来看一段伪代码
for(s1 = s; s1 >= 1; s1 = (s1 - 1) & s)
首先我们可以知道枚举出来的s1必然是s的子集,因为s是s的子集,一个数&s的结果肯定也是s的子集
接下来我们考虑为什么s1可以枚举完所有s的子集(除了空子集)
s1 - 1,相当于将s1中最右侧的1变成0,同时将其右侧全部变为1.&s相当于把右侧中不在s里的1都去掉。
所以一次迭代相当于s1中最右侧的1去掉,同时还原这个1右侧的在s中的所有1,不影响被去掉的1的左侧的1
那么我们考虑每次迭代s1必然是减小的,且最后一定会减小到0,同时每次操作最多只会删除1个已有的1,可能还原出一堆1.
所以我们可以知道,对于s中的任意一个1,必然存在某一时刻,它被删除了,而它左边还没开始删,那么此时它右边的1都会被还原出来。
也就是说这个时刻相当于s中删且只删了这个1.
因为对于任意一个1都存在这个时刻,因此我们可以知道s只去除1个1的子集肯定是枚举完了。
然后我们用递归的思想理解一下。既然我们对于s只去除一个1的子集都枚举完了,那么我们在枚举到这些只去除一个1的子集的时候,相当于将这个子集作为新的s往下枚举。
那么我们又可以枚举完这个子集所有只去掉一个1的子集,也就相当于s只去掉两个1的子集……
以此类推,我们可以知道最后我们可以枚举完s去掉任意个1的子集。
常见的状态变换
对s的第i位进行操作
令\(k = 1 << (i - 1)\)即可得到一个第i位为1,其他位为0的二进制串,然后用这个二进制串和s进行操作即可。
比如:
判断第i位是否为1:s & k > 0 则第i位为1,否则为0
将第i位改为1:s = s | k
将第i位改为0:s = s & ~k (此时k的第i位为0,所以可以强制s的第i位为0,k的其他位为1,因此不会影响s的其他位)
去掉s最右的1
s = s & (s - 1)
这里类似于前面的子串枚举了。
s - 1得到去掉最靠右的1且该1的右侧全变为1的二进制串。
就是上文中
所以我们可以知道,对于s中的任意一个1,必然存在某一时刻,它被删除了,而它左边还没开始删,那么此时它右边的1都会被还原出来。
也就是说这个时刻相当于s中删且只删了这个1.
这个任意1为最右侧1的情况。