帮助——状压

帮助


这道题好题,接下来我们从头到尾详细地分析一遍这道题的做法以及给我们以后做题目的启发。


考虑到(25)(32)这一值域很小,并且可以整体平移到(0)(7),这是原始想法。


接下来积累一个比较好的想法:将一个数抽出来再放回去相当于仅仅把抽出来的数一股脑取出来,最后再进行插入操作

该转化有何好处呢?

  1. 可以将复杂的问题分步解决;2. 转化为较为通用的问题:也即,我们将原问题转化为了从一个序列删一些数,再加一些数求最值。

该转化可以被广泛地应用。

回到本道题,我们可以将问题视作:先从这一堆数中取走若干个数,再将这若干个数插回去,在该过程中,如果取出的一些在数值上相等,按照贪心来讲,插入回去的时候必定在一起,因为这样最优。


有了这样的想法,我们不妨定义状态:

[dp(i,j,0/1) ]

分别代表前(i)个数,还剩(j)次操作能使用,是否被取走的最小混乱度。

接着我们转移,发现:

  1. 诶?如果我们选择取走,那么直接转移,但是最小混乱度还需要加上我们取走的数重新加入原序列中造成的混乱度;
  2. 那若我们不取走,就会发现若上一个数选择取走,我们整个状态转移没办法进行。

因此,我们不难想到可以更加细致化状态。


再考虑数据范围,诶?(1)~(8)?这么小。

我们可以给原来的状态加一个维度.

[dp(i,j,S) ]

其中(S)代表包括(i)在内前面被取走数的集合。

那么我们有:

[dp(i+1,j+1,Scup{a_{i+1}})=min(dp(i,j,S))\dp(i+1,j,S)=min(dp(i,j,S))+w ]

第一个方程很容易理解,而第二个方程中的(w)是啥?

我们考虑,当(a_{i+1})(a_i)数值相同时,(w)就等于(0),如果不相同,(w)就等于(1)

且慢

如果(a_i)这个数被取走了呢???我们便不是这么好办了。

因而,我们需要再加一维进行描述。

所以,我们能这样吗:

[dp(i,j,S,0/1) ]

集百家之所短

它的问题出在了我们没办法直接确定上一个保留的数是否与之相等。

那么,我们这样呗:

[dp(i,j,S,pre) ]

(pre)代表上一个被保留的数的位置。

已经可以搞了。


[dp(i+1,j+1,Scup{a_{i+1}},pre)=min(dp(i,j,S,pre))\dp(i+1,j,S,i+1)=min(dp(i,j,S,pre))+(if (a_{pre} ot=a_{i+1})1; else 0) ]

那么,这个式子就可以通过了吗?别忘了滚动数组啦。

那最终答案怎么表示呢?

好问题,不太好解决——保留的数的混乱度加上取走的数重新排队的混乱度,我们记录的集合(S)好像不能记录保留的数中有与之相等的数。因为如果原序列中有相等的数,那么取出来的数最优的情况是和该数接在一起生活。

第二个思想:补集转化。

既然我们知道取出来的数集合作为状态的一维不利于统计答案,那么我们可不可以使保留的数作为状态的一维?

这是可以实现的。并且答案就是所有数值的集合减去未被取出来的数值集合剩下的数各成一峰。

总的时间复杂度为:(O(n^2*k*2^7)),卡一卡常就过了。


其实进一步优化:

考虑到我们最后那一维其根本我们是来解决上一个未取出的数的数值是否与当前不取出的数相等。

因为数的值域那么小,我们仅记录上一个数的数值是多少即可。(O(n*k*2^{11}))


我们总结一下:

  1. 我们考虑到原问题的值域较小,因此我们平移该值域;
  2. 将取出一个数再放回去的动作转化;
  3. 将状态精细化,这也是DP常见的做法;
  4. 补集转化思想,将取出来转化为不取出来;
  5. 状态精简。

这个问题就这样迎刃而解了。

原文地址:https://www.cnblogs.com/zach20040914/p/14401115.html