白书动态规划例题和习题简解
Pre言:
本文的题解原题均来源于白书,题号:
UVa 10859, 11825, 11584, 10534, 11552, 11404, 11795, 10564.
LA 3983, 4794, 4256, 4731, 4727, 2038, 4394, 4015.
一直觉得自己的基础DP不是很扎实,所以就花了一节晚自习+半个上午的时间阅读和解决了上述大部分题目,少数几道习题查了题解之后弄懂,还有一两道搞了很长时间。。作为不能停课狗,大部分题目代码就没写了。。写了代码的题目单独开了几篇博客,都在DP分类里。
下面进入正题——
【UVa11825】
非常经典的图上整体影响模型。这类题目的通常处理方法是把“命运共同体”作为整体。记得黑书上有一道非常经典的例题,就是以每一个节点列一个xor方程,最后用高斯消元求解。本题是这种模型的一个变式。但我们依然首先使用整体法,把每个节点及其相邻节点作为一个集合。题目要求让尽可能的服务完全瘫痪,所以我们把这些集合再划分成若干个集合(姑且称之为“大集合”),使得每个大集合中包含所有节点,我们的目标就是最大化大集合的数目。这个时候就需要用到集合DP了。设f[S]为大集合S最多可以分成多少组,则f[S] = max { f[S-S0] | S0是S的子集且覆盖了所有节点 } +1。如何判断一个大集合是否覆盖了所有节点呢?只需枚举每一个大集合,预处理出来即可。小集合的数目是n,所以大集合的数目约为2^n,所以整个算法的时间复杂度sum{ C(n, k)*(2^k) } = 3^k,可以承受。
【UVa 11795】
和上面那一题一样,也是一个集合DP,但要裸得多。一种最朴素的状态是设f[W][R]表示当前手中有集合W武器,集合R未消灭机器人时的顺序数。但这个状态数是4^16,MLE+TLE。然后我们考虑状态之间的制约关系,会发现武器和机器人是一一对应的,所以可以把机器人都换成武器,把两维分开即可。时空复杂度都为O(2^n)
【LA 4794】
一道比较综合的题。。n的范围只有15,所以我们考虑集合上的DP。什么集合呢?题目已经指明了:面积的集合。我们设f[r][c][S]为r行c列的巧克力是否可以切分成集合S。根据题意,我们可以横着切一刀或者竖着切一刀。所以,f[r][c][S]=1当且仅当 f[r0][c][S0]=f[r-r0][c][S-S0]=1(横切)或f[r][c0][S0]=f[r][c-c0][S-S0]=1(竖切)。但此时状态有O(xy*(2^n))个,太多了。所以我们再考虑状态之间的制约关系。如果r*c≠sum(S),那么该状态的值一定为0。这样,就只需要保留r*c=sum(S)的状态。因此,不妨设r<=c(对称性),那么就可以把状态简化成f[r][S],状态数目减为O(x*(2^x))。状态转移时,我们枚举S的子集,即可确定c的大小,时空复杂度均可承受。此外,还需判断sum(A)是否为R*C,否则一切的状态定义都是没有意义的。
【LA 3983】
其实这题就是斜率优化,但是Rujia Liu讲得非常通俗,动机、解决方案一气呵成。
【UVa 11584】
一个经典的问题。设f[i]表示前i个字符最少划分成多少个回文串,则f[i]=min{ f[j]+1 | j+1..i是回文串, j<i }。如何判断一段是否是回文串呢?预处理。方法是枚举中心并往两边拓展。时空复杂度均为O(n^2)。
【LA 4356】
看起来无从下手…… 但注意到数据范围非常小,所以可以考虑细致地描述状态:f[i][j]表示前i个字符已修改完毕,且第i个字符修改为j时的最小修改次数。当A[i]=j时,无需修改,否则枚举在图上与前一个数字相邻的数字,取最小值即可。
【UVa11552】
和上面那题非常像。首先一个组中相同的字符显然应该放在一起,所以可以缩成一个字符。设f[i][j]为前i-1个组已处理完毕,且第i组结尾字符为j时的最小块数,按题意DP之即可,没有什么坑。
【UVa10534】
即经典的合唱队形问题。从左到右、从右到左做一遍LIS,最后枚举中间点取最大值即可。
【UVa11404】
这题题号不太吉利啊(雾。。)。这题看了白书上的提示才想出来的(羞耻MAX...)。不过真的非常巧妙。。枚举i,把S[1..i]和reS[i+1..n]做LCS,取最大值即可,其中reS意为把这个串翻过来。但还要注意奇偶性——这个方法求出来的串只是偶串。如果这个LCS没有选S[i]和reS[1],则其长度还可以加1以构成奇串。如何知道有没有选呢?而且,题目还要求求字典序最小的,如何处理?只需记录每个有效状态转移的前驱即可,这里的有效状态指的是A[i]=reS[j]的状态。事实上,我们只利用有效状态之间的转移,非有效状态可以作为过渡。(感觉像这种题我得调上两三天……
【LA4731】
首先需要一点推(cai)理(xiang):概率较大的应该尽量往前放。这个证明也很简单,略去。所以我们就可以设计出状态:f[i][j]表示概率前i大的区域分成j组的期望,那么第i个区域可以和前面的若干连续区域放在一组,也可以从它开始单独开一组。我们枚举第j组从第k区域开始,那么就有f[i][j]=f[k-1][j-1]+sum[k][i],其中sum可以用前缀和预处理一下,O(1)即可得到。