浅谈状态压缩DP
本篇随笔简单讲解一下信息学奥林匹克竞赛中的状态压缩动态规划相关知识点。在算法竞赛中,状压(DP)是非常常见的动规类型。不仅如此,不仅是状压(DP),状压还是很多其他题目的处理技巧。所以掌握状压、掌握状压DP是十分重要的。
注:虽然自己写的也是状压DP的讲解。但还是凭良心推荐机房大佬@littleseven的状压博客,讲的真的是太详细了。强烈推荐!
链接Link:
状态压缩的概念
来看一个问题。
相信大家都做过动态规划的背包问题,那么我们再来看一个跟背包很像的一个问题:
现在对于一个大小为(V)的背包,有(n)个物品,每个物品有一个价值和一个体积。请问:每种放置方法能达到的价值。
最暴力的方法是(n)重循环,每重循环只有两次,一个是判断,一个是不判断,复杂度是(O(2^n))的。
但是这种方法并不是非常棒的,因为在编写程序的时候,循环的层数是确定的。但是(n)是一个变量,如果非要按这个方法写的话,就会有一堆判断(分别判断(n=1,n=2,n=3,...,n=maxn))的情况,麻烦得要死。
那怎么办呢?
我们可以发现:对于每一种方法中的每种物品,都只有选或者不选两种选择。这就是一个决策过程,因为我们要知道所有方式的价值,那我们莫不如用一个二进制数表示这个状态“选还是不选”,二进制数对应的位就表示这个物品选还是没选,然后在二进制基础上进行计算价值。
所以枚举范围就是(0-2^n),从一个都不选到全都选。
那么我们发现,原本需要开若干重循环和若干维数组的问题一下子变成了用一个数组和一重循环就解决的问题了。
这个把状态压缩的过程就叫做状态压缩,在存储状态的基础上进行枚举转移的动态规划就叫做状压DP。
状压DP的前置知识
因为状压是在二进制的基础上实现的一门DP算法,所以它一定会和位运算相关。事实上,所有状压(DP)都需要选手具有高深的位运算技巧,熟练运用位运算取出或设置每一个状态的判断条件和转移。
如果对位运算还不太懂的同学,请参考这篇博客:
并且,我们要对动态规划的过程有一个直观的认知。
有了这些知识,理解状压(DP)才会更加流畅、顺利。
状压DP的性质及特点
通过刚才的例子(背包),我们会发现,状压对算法的时空复杂度并没有多少的优化,也就是说,它的本质其实是暴力枚举。
因为是暴力枚举,所以枚举每个状态的时间复杂度是(O(2^n)),这是一个指数级别的时间复杂度。高中数学必修一的函数部分告诉我们,指数是爆炸式增长的,也就是说如果不加优化,时间马上就会炸掉。所以基于这个性质,一般来讲状态压缩的题数据范围不会超过(20),数量比较小。而基于这个性质,我们设置状态的时候也就挑这个最小的数来设置。
状压DP的解题思路
先来看一道状压基础题(是洛谷蓝题,但是因为状压都比较难,所以不用发怵)
题目大意:
给定一个(N*M)的矩阵,要在这个矩阵中种草,规定入股矩阵的值俄日1就可以中,反之就不行。现在给出这个矩阵,请问它有多少种猪矩阵的方案。数据范围:((1le N,Mle 12))
思路分析:
看见没,数据范围非常小,这样的题往状压上想一想。因为要统计方案数,所以我们的状态就设置成:(dp[i][j])表示枚举到第(i)行,第(i)行的状态为(j)时的方案数。那么答案就是第(n)行的所有状态方案数的和。
刚刚我们已经说了,状压其实连优化枚举都算不上,它就是方便了我们写枚举的循环代码,对复杂度并没有多少贡献。基于这个性质,我们直接开始从头枚举,依次递推即可。
但事实没这么简单,因为我们还需要判断每个状态是否合法:如果不合法,那这个状态就不能进行枚举。
所以我们再想办法在转移的时候判断其到底合不合法即可。具体的实现请翻上面的题解。
总结:
状压(DP)算法的设计思路为:(敲黑板!!)
设计状态—>考虑转移的先决条件—>想办法维护这些条件—>进行转移—>统计答案。
状压DP例题
以上为基础状压DP,以下为状压DP进阶