状态压缩DP专题
两道入门题目:
1.2305. 公平分发饼干
1.状态定义:dp[i][j] 为第 i个孩子分饼干状态为 j 时每个孩子能分到的最多饼干数的最小值
2.状态转移:要求得dp[i][j] 的值,要考虑 j 的每个子集,再维护 子集计算出的最大值然后转移过来 最小值
dp[i][j]=min(max(dp[i-1][j-x],sum[x])) 其中sum[x]为分配状态为 x 时的总的糖果数
3.初始化:dp[0][j]=sum[j],只分给第一个孩子肯定是全分了总数就是饼干数sum[j],其余为 INF 方便覆盖
4.遍历顺序:先i后j最后x,正序
5.返回形式:返回 dp[n-1][mask-1] 即 所有孩子将饼干全部分完时每个孩子最大饼干数的最小值
代码如下:
class Solution { |
2.1723. 完成所有工作的最短时间
代码如下:
class Solution { |
我们不妨来总结一下状态压缩DP:其实状态压缩DP是类似于暴力法回溯的方法,能用状态压缩方法做的通常都可以用回溯+剪枝来求解。一般来说这种问题称为“分配问题”,或者叫做“桶轮询”。就是将元素(通常数目很小<32个)分配到多个容器(桶)中,然后求解有每个桶最多数目的最少值,或者是满足条件路径数等,这里求解目标的不同体现在转移方程上。
抽象一下:饼干、工作(要分配的对象)——元素;工人、孩子(被分配到的地方)——容器(桶)
526. 优美的排列 这道题就是要求路径数目,同时将桶容量限制为1,因此子集数目只有 i 个
3.总体模板(本质也是DP):
1.状态定义:dp[i][i]为考虑前 i 个容器(桶),分配状态为 j (0101表示分配状态)时候的 所求量(路径数、最大值、最小值等)
2.状态转移:此时遍历到第 i 个容器(桶),一般来说要求 dp[i][j] 得考虑前面容器的情况 -> dp[i-1][j-x]
其中 x 为第 i 个容器的选择状态,那么 j-x 就是 [0,i-1] 个容器的选择状态
将第i个容器独立出来考虑,这个容器的选择有哪些?也就是转移路径有哪些???
很显然如果桶的容量(包括元素个数与总和)没有限制的话,j 的全体子集(除了本身)都是符合要求的前一个状态
-> 因此可以直接通过下面语句枚举 j 的所有合法子集来进行 dp[i][j] 状态转移
for (int x = j; x != 0 ; x = (x - 1) & j) { |
-> 当然也有可能容量有限:参考 526. 优美的排列 枚举符合条件的子集(合法转移路径)进行转移即可
3.初始化:一般来说,初始化 dp[0][j] 为首个容器分得状态 j 时的所求量;其他dp[i][j] 按照转移逻辑来初始化
目的都是要作为初始哨兵不影响第一个值的覆盖(如求最大值就弄个很小的数第一个比较的必定顺利覆盖…)
4.遍历顺序:一般是先遍历容器 i ,再遍历每个状态 j ,最后遍历每种合法转移路径 j-x,默认正序
5.返回形式:一般返回 dp[n-1][mask-1] 表示考虑所有桶,把所有元素分配完的所求量为多少