状压DP算法
状压DP
使用状压DP的原因
我们知道状态压缩,顾名思义,就是需要考虑的状态非常多,我们如果用平常的思想去表示状态,那是非常不现实的,在时间和空间上都不允许,我们使用某种方法,以最小的代价表示某种状态。 那么,这通常是用进制来表示状态的,而选择几进制则根据要求使用的对象的点的状态有几种。一般来说,只有0和1,我们则是用二进制来表示,当然也有其他进制的题,在例题中会列举,需要我们灵活变通,主要谈二进制。
那么如何用二进制表示状态呢?我们发现,二进制上是按位分的,那么我们每一位可以看成一个点,而点上的取值则为该点的状态或者选择。例如00001001这个状态则表示第一个点和第四个点状态为1,其余的点状态为0。所以按照这种思想,能抽象的表示出一个很复杂的状态,实现了时间和空间的优化。
状压DP的适用条件
状态压缩其实是有适用环境的:
- 状态需要有一定的状态单元。 即一个状态应该是保存一个集合,其中的元素值对应着0或1,例如我们常见的棋盘,我们可以用0或1来表示棋子的放置状态。而整个集合即是一个01串,即二进制数,我们通常用十进制表示。那么我们再进行状态转移或者判断的时候,需要先将十进制转化为二进制,再将二进制转化为十进制。
- 题目中限制的集合大小不会超过20。 这是最显著的特征,为什么呢?我们知道如果用二进制表示状态,那么集合大小为20的二进制状态有2^{20} - 1已经达到1e7的数量级了。
- 具有动态规划的特性。 对于动态规划,一般都是要求最优化某个值,具有最优子结构的性质。同时也需要满足状态转移的特性,而不是前一个状态毫无关系的。
适用环境总结:
- 二进制
- 集合/状态大小受限(比如不会大于int类型能表示的最大数)
- 满足动态规划特性
状压DP的板子
1 | int n; |
例题
USACO06NOV Corn Fields G
农场主John新买了一块长方形的新牧场,这块牧场被划分成M行N列(1 ≤ M ≤ 12; 1 ≤ N ≤ 12),每一格都是一块正方形的土地。John打算在牧场上的某几格里种上美味的草,供他的奶牛们享用。
遗憾的是,有些土地相当贫瘠,不能用来种草。并且,奶牛们喜欢独占一块草地的感觉,于是John不会选择两块相邻的土地,也就是说,没有哪两块草地有公共边。
John想知道,如果不考虑草地的总块数,那么,一共有多少种种植方案可供他选择?(当然,把新牧场完全荒废也是一种方案)
输入格式
第一行:两个整数M和N,用空格隔开。
第2到第M+1行:每行包含N个用空格隔开的整数,描述了每块土地的状态。第i+1行描述了第i行的土地,所有整数均为0或1,是1的话,表示这块土地足够肥沃,0则表示这块土地不适合种草。
输出格式
一个整数,即牧场分配总方案数除以100,000,000的余数。
输入
1 | 2 3 |
输出
1 | 9 |
思路:
我们先作出规定,定义n代表的是行,m代表的是列。那么牧场大小就是n × m。我们看到数据范围,n , m都特别小,同时所求为方案数,这很符合状压DP的适用条件。那么对于每一行,我们就可以看成一个未知集合,而集合的大小自然就是列m。对于每一个单元,其取值范围为0 , 1,而1代表放置奶牛,0代表不放置奶牛,所以我们自然可以用二进制表示,那么状态总数就是( 1 < < m ) − 1。
- 对于每一个状态,我们需要判断是否合格,而其中明确不能选择两块相邻的土地,在集合内,即相邻位不能全为1,所以我们可以预处理g数组,处理方式即为:g[i] = !(i & (i << 1))(这里的i的取值从0到所有的状态);
- 同样,我们还应该知晓土地的状况,因为毕竟只有土地肥沃才可以放置奶牛,则我们可以通过一个st数组判断,集合与集合之间,我们也需要考虑相邻位不能全为1,所以在枚举上一个集合的状态也需要严格判断。
- 对于状态定义,我们可以用f [ i ] [ j ]表示第i行且状态为j的方案数。
- 对于状态转移,假设上一行状态为k,则状态转移方程为:f [ i ] [ j ] + = f [ i − 1 ] [ k ]
答案:
1 |
|
糖果
【问题描述】
糖果店的老板一共有 M 种口味的糖果出售。为了方便描述,我们将M种口味编号1~M。
小明希望能品尝到所有口味的糖果。遗憾的是老板并不单独出售糖果,而是K颗一包整包出售。
幸好糖果包装上注明了其中K颗糖果的口味,所以小明可以在买之前就知道每包内的糖果口味。
给定N包糖果,请你计算小明最少买几包,就可以品尝到所有口味的糖果
【输入格式】
第一行包含三个整数 N、M 和 K。
接下来 N 行每行 K个整数 T1,T2,….Tκ,代表一包糖果的口味。
【输出格式】
一个整数表示答案。如果小明无法品尝所有口味,输出-1。
1 | public static void main(String[] args) { |