zoukankan      html  css  js  c++  java
  • 笔记 dp套dp

    笔记 dp套dp

    本篇侧重 理解

    从本质入手

    dp的本质: 我们有一个要解决的问题,把问题分成若干步(dp的阶段), 每一步都有一个状态表示(dp的状态),然后考虑状态间的转移。dp把问题通过某种顺序解决,使得我们可以 不记录 一些东西,而只记录我们关心的东西,以压缩我们描述状态用的东西

    简单的说,就是我们把研究的问题的特征打包变成一个节点,节点连成DAG(通常),然后我们在DAG上拓扑的跑,就是dp的转移。

    进一步思考

    现在有一个能dp的问题,给定一个答案,求有多少种问题,使得输入它能得到给定的答案。

    答案不一定是一个简单的数

    首先我们必做的一件事情是,把这个问题的dp搞出来 (这个不搞后面咋搞)

    接下来,能得到“答案”,还能如何变形?可以想到,把它变成能得到(内层dp的某一些) “状态”/“节点”

    一拍脑袋觉得很有道理,接下来我们就有了设计外层dp的大方向:我们用和内层dp类似的方式拆解原问题,但是在每个阶段里,我们不去求答案,而是算有多少种输入能使得内层dp的值,长成某个我们想要的样子。

    那外层dp的大概样子就是 (f(U,P)) 表示,内层dp考虑到 (U) 节点,我们要研究的(若干个)内层dp值(打个包)是 (P) ,的方案数。

    为啥要考虑内层dp的节点?

    • 因为我们和内层dp一样拆问题,就比如序列问题,内层dp是一位一位考虑,那我们外层的dp也一位一位的考虑,这时候我们的状态里就会和内层dp一样,有一个当前位置 (i)

    为啥我们要可能研究不止一个内层dp值?如何打包?

    • 这个看内层dp的转移,如果要用到很多前驱dp值,那我们可能就要把一些dp值,比如说一行的dp值,给打包一下
    • 打包方法最常见的就状压吧,其它的方法和状压也差不多,比如可能会有三进制状压之类的

    检验真理

    来点实践。

    bzoj3864 Hero meet devil

    给定一个串 (S(|S|le 15)),和一个 (m)。对于 (k=0...|S|),要求有多少串 (T) 使得 (|T|=m)(LCS(S,T)=k)。(这里LCS是子序列)

    考虑LCS的dp,(f(i,j)) 表示 (LCS(T[:i],S[:j]))。这个显然可以转移,但是我们发现更多性质,就是 (f(i,j+1)ge f(i,j)),但是 (f(i,j+1)le f(i,j)+1)。这两个都很显然,但是这代表 (f(i,*)) 的差分数列就是个 0/1 数列。

    那我们就可以压了(注意到 (|S|le 15),显然可以压)。根据上面那一套思想,我们外层 dp 就这么设计:

    (f(i,M)) 表示 (T)(i),这一行的内层dp数组做差分后压缩一下为 (M),的方案数。

    你可能会问,原dp中的j哪去了。 注意到我们直接把一整行打包了,所以这个j就没了,并且我们转移的时候是直接一整行转移的。

    接下来我们考虑 (T)(i+1),加了某个字符。但是加上字符的变化,可以推一下,和 (i) 并没有关系,只和这一行的 (dp) 状态 (M) 有关。那我们可以很容易的从 (f(i,M)) 转移到 (f(i+1,M')),最后随便统计一下就得到答案了。

    TopCoder StringPath

    给定两个长为 (n+m-1) 的串 (A,B),问有多少个 (n imes m) 的字母矩阵,使得它恰好村则两条从左上到右下的路,一条的字符连起来是 (A),另一条是 (B)

    同样用类似的套路,先考虑内层dp。

    内层的dp是用一个二维的 (dp[i][j][2]) 记录到 ((i,j)) ,是否能匹配 (A/B) 的前 (i+j-1) 位。

    接下来考虑外层dp。就是考虑有多少个字符矩阵能到达某个内层dp的状态。内层dp的值我们发现它就是两个0/1,果断状压。

    然后我们和内层一样,按枚举坐标 ((i,j)) 考虑,并想怎么转移到 ((i,j+1))。我们发现这个东西的转移和左,上有关,并且还要可延续(即不能只记录它的左,上),所以我们压0/1压的是轮廓线,一条轮廓线存 (m) 个,而因为有 A,B 两个串,所以要存两条。

    这里有个小细节,这个轮廓线应该是包含 ((i,j)),即 ((i,j+1)) 头上的那条轮廓线,如下图,如果红色表示 ((i,j)),那我们记录的轮廓线是蓝色这一带。

    image-20210629220638864

    实现的时候,我们直接拿状压的bitmask的(从低到高)第i位,表示轮廓线从左到右的第 (i) 个,而不是从上面开始数,一直顺序记到 ((i,j)) 为止

    也就只有我这种傻逼会想到后者这种阴间记法吧

    这样我们就有了外层dp的状态,(dp[i][j][A][B]) 表示到 ((i,j)) 位置,内层的dp的值的轮廓线状压起来是 (A,B),有多少种。根据内层dp的轮廓线,很方便转移。然后这题就没了,复杂度 (O(nm2^{2m}))

    总结

    dp套dp,它就是在原来的dp转移DAG上dp。

    尽管我大概懂了它是个什么东西,但是我练的还太少,写题不够熟练

    看提交记录里面一堆WA就知道了

  • 相关阅读:
    Android 原创新作 超级水平仪 发布
    幸运转盘v1.0 【附视频】我的Android原创处女作,请支持!
    Android中MediaPlayer播放音乐时自动中断的解决办法
    语音写字板 v1.01 我的第二个Android作品
    Eclipse导入Android项目的正确方法
    Silverlight中后台代码设置TreeView选定项的方法
    C#网络编程(基本概念和操作) Part.1
    C#网络编程(同步传输字符串) Part.2
    C#网络编程(接收文件) Part.5
    C#网络编程(异步传输字符串) Part.3
  • 原文地址:https://www.cnblogs.com/LightningUZ/p/14952274.html
Copyright © 2011-2022 走看看