一类动态规划问题状态的简化

DP学习心得—状态的设计与简化

其实是带着一些奇怪的想法找了点题目。

动态规划由两个重要的部分组成:转移方程状态
好的状态是动态规划能够发挥威力的重要前提,而好的转移方程则是动态规划发挥作用的途径与方式
因此,通过一段时间的学习,本人试着总结出一些动态规划问题的状态上的设计思路与巧妙化简,并写下这篇总结供日后学习所需。


Part 1 状态的设计

好的状态是动态规划的开始,一个动态规划问题中的状态应该首先满足:

  1. 能够准确地描述对转移有影响的状态维度,不存在不同转移情况的混淆(即转移过程不遗漏,不混淆)。
  2. 不具备后效性,即不会存在互相转移的情形。
  3. 能够满足转移时最优性的传递(即最优子结构性)。

在满足了以上几点后,我们基本可以得到一个保证正确性的状态。
但是有时候,仅仅这样是不够的,我们的状态可能会显得过于冗杂,此时则需要我们用一定的技巧来转变思考角度,减少冗杂的成分,来让转移更加的轻简。

Part 2 状态的简化

状态的优化大致上分为两种类型:修正型和简化型。
接下来将从两个方面,通过一些例题来总结一些思路与方式。


首先是两道较为简单的基础优化。(注意是优化较为基础)

Sharing Chocolates

题意简述:给定两个正整数 (n,m) ,问是否能够将一个长,宽分别为 (n,m) 的矩形通过每次沿长或宽的方向分成两个子矩形,使得最后恰好得到 (k) 个子矩形,且面积分别为 (a_1,a_2,a_3,...,a_k)
(tips:n,mle100,kle15)

首先,我们考虑一个较显然的 DP 思路。既然 (k) 只有小小的 (15) ,我们考虑把当前矩形最终可以得到的 (a_1,..,a_k) 中的哪些面积当成一维写入我们的状态,这样不难得出一个状态:

[dp[i][j][sta] ]

表示矩形的长为 (i) ,宽为 (j) 时,是否可以通过划分得到 (sta) 中的面积。
这样一来,我们就可以得到一个总数为 (O(mn2^k)) 的状态。
但是这个状态在空间上就已经难以接受,因此,我们需要考虑优化。

注意到一点:如果一个状态 (sta) 中所有元素之和不为 (icdot j),可以直接为 (false)
这是不难理解的,因为我们的要求是恰好得到,如果面积都不等就不可能恰好得到了。
这样一来,我们便有了一个优化思路:我们可以通过 (sta)(i) 的值来直接算出 (j) !也就是说状态中的 (j)可以被其他维度表示的多余维度。发现这个后我们就不难修正出新的状态了:

[dp[i][sta] ]

表示当前矩形较长的一条边长度为 (i) (这样规定方便处理),是否可以通过划分得到 (sta) 中的面积。
这样,我们的状态总数就降到了 (O(max(n,m)cdot2^k)) ,可以满足空间的要求了。

Blinker 的仰慕者

题意简述:给定两个正整数 (L,R) ,求区间 ([L,R]) 中满足各位数字之积等于 (k) 的数的和。
(tips:L,R le 10^{18},k le 9^{18})

这道题的状态是不难考虑的,也是很套路的数位 DP
(dp[i][j]) 表示考虑了前 (i) 位,当前数字各位乘积为 (j) 的数字之和;
(f[i][j]) 表示考虑了前 (i) 位,当前数字各位乘积为 (j) 的数字个数。
则我们可以很方便地写出转移方程...慢着!
(k) 不是最大可以有 (9^{18}) 吗,这怎么直接弄进状态了?!

是的,这样大的数据范围不允许我们把乘积写入状态,但是又不可以直接把这一维给省略掉,这该如何是好呢?
幸运的是,我们发现乘积只会由 ([1,9]) 中的数字相乘而来,这就意味着每一个乘积都可以被分解为 (2^acdot3^bcdot5^ccdot7^d) 的形式!
而通过计算得知,([2,10^{18}]) 内满足此条件的数不超过 (10^5)
于是,我们可以预处理所有的 (2^acdot3^bcdot5^ccdot7^d) ,然后就可以把大量的无用状态通过离散的方式舍去,这样就可以通过这道题了。


完成了这两道试手题,我们来看些有难度的。

义已失吾亦死

题意简述:给定 (n,a,b,c,p) ,求用 (a)(1)(b)(4)(c)(5) 能够组成的最大的能被 (p) 整除的 (n) 位自然数。
(tips:a,b,c,nle233,ple64)

这道题乍一看不蛮好想,但我们大概确定了方向:数位 DP
接下来考虑状态的设置。
首先我们可以排除掉最优性 DP,因为数位在这个问题中并不具备最优子结构(后面尽可能大不一定最后也是最大)。
那么我们考虑设状态 (dp_{i,a,b,c,q}) 表示考虑了前 (i) 个数位,还剩余 (a,b,c)(1,4,5) ,当前组成的数模 (p) 的值是否可以(q)
那么我们就可以考虑 DP 计算了,最后统计答案可以贪心地在每一位尽可能往高了选。
但是有一个问题:状态总数是 (O(n^4p)) 的,过不了怎么办?

方法一:简化状态

发现一个细节:我们可以直接通过 (a,b,c) 来算出 (i) 的值来。
这就帮助我们消去了许多无用状态,总数为 (O(n^3p))

方法二:压缩状态

我们每一个状态表示的都是是否可以的形式,意味着它的取值只有 (0/1)
那么不难发现大量空间被浪费了,又注意到 (ple64) ,所以我们考虑把状态变为 (dp_{a,b,c}) 表示已经用了 (a,b,c)(1,4,5) 后组成的数模 (p) 的余数的集合,即一个状态压缩后的整数((64) 位,每位为 (0/1) 表示是否存在模 (p) 后为这个数的数)。
这样的话状态总数成为 (O(n^3)) ,总的复杂度就会降为 (O(n^3)) ,可以通过本题。

Easy Climb

题意简述:给定一个长度为 (n) 的序列 (a) ,每次可以选择一个数 (a_i(i in [2,n-1])) 花费 (1) 的代价把它变成 (a_i+1)(a_i-1) ,求使得对于任意 (iin[1,n)) 满足 (|a_i-a_{i+1}|) 不超过 (d) 的最小代价。
(tips:2le nle100,0le d,a_ile10^9)

这个题首先有一个很显然的暴力 dp 的思路。
(dp[i][j]) 表示考虑了前 (i) 个数,使得当前数的大小为 (j) 的最小代价。
那么我们就可以快乐的 (O(nd^2)) 计算啦!(离谱)
那我们就应该考虑怎么优化。
首先我们并不可以离散化,因为它的修改是任意的,无法直接离散化。
考虑把状态的冗杂部分去除?但是这两个状态都是不可抹去的,不能互相推导。
或许考虑换一下状态的定义?在本题中这种方法并不适用,无法起到优化复杂度的作用。
这个时候,我们就应该进行对问题的分析,运用贪心思想来优化状态。

假设现在有三个数字 (a,b,c) ,考虑 (b) 的变化范围应该是: ([max(a,c)-d,min(a,c)+d])
那么 (b) 的取值就只有三种情况:

  1. 不变。
  2. (max(a,c)-d)
  3. (min(a,c)+d)

由此我们可以发现一个结论: (b) 的最后结果一定可以表示为 (p+kcdot d) ,其中 (p)(a,b,c) 中的一个数。
那么这个想法是否适用于 (a_1,a_2,...,a_n) 呢?答案是肯定的。
即:所有 (a_i) 最后都可以表示为 (a_p+kcdot d) ,其中 (pin[1,n],kin[-2n,2n])
这个想法的正确性证明参考这篇博客(不想画图)。
那么就可以通过贪心思想来帮助我们实现离散化的操作。
(dp[i][j]) 表示考虑了前 (i) 个数,使得当前数的大小离散化后为 (j) 的最小代价。
那么状态总数就是 (O(n^3)) 的,已经优化了不少,下一步是很经典的单调队列优化,不再赘述。


Summary

综合来看,dp 的状态简化一般有如下方法:

  1. 消除冗杂量。
  2. 状态离散化。
  3. 位运算压缩状态。
  4. 贪心等其他算法辅助。

一般的针对状态的优化大概就是这四种类型,其中除最后一点需要深入思考题目性质外,其他三点都是比较容易发现的,当转移方面已经无路可走时,不妨想一想能否从状态本身下手来进行优化。

原文地址:https://www.cnblogs.com/tuifei-oiers-home/p/14226145.html