区间 DP

概述

  • 区间 DP 是以 dp 设计从区间出发为特点的一类 dp。

  • 状态设计往往包含 \(l,r\)

  • 初始化通常为 \(dp_{l,l}=w_l\)

  • 转移则不一而同,看下面的详解吧。

  • 实现倒是比较统一,比较喜欢用记搜,毕竟记搜(在不考虑进/退函数开销的情况下)比较快,而且好写。否则需要枚举 \(len\)

  • 我们下面主要分五类来谈。

分裂式问题

  • 典型代表如 \(\text{P1880 [NOI1995] 石子合并}\)

  • 特点是其 dp 设计往往从“枚举断点把整个区间分成两部分,分别处理后合并”的分治思路出发。

  • 状态通常为 \(dp_{l,r}\),表示处理完毕 \(l\sim r\) 的最大/小价值。

  • 初始化通常为 \(dp_{l,l}=w_l\)

  • 状态转移方程通常为 \(dp_{l,r}=\max/\min_{k=l}^{r-1}(dp_{l,k}+dp_{k+1,r}+cal(l,r(,k)))\)

    • 其中 \(cal(l,r(,k))\) 是可能与 \(k\) 无关,也可能与 \(k\) 相关的一个计算合并价值的函数。

    • 如果与 \(k\) 无关,那么很可能可以使用四边形不等式优化。

    • 也有的题目中 \(k\) 是分水岭,不属于任何一边,如 \(\text{P1040 [NOIP2003 提高组] 加分二叉树}\)

  • 通常复杂度为 \(O(n^3)\),适用四边形不等式的情况可以 \(O(n^2)\)

P1880 [NOI1995] 石子合并

  • 题意:有 \(n\) 堆石子构成一个环,每次可以取相邻的两堆合并成一堆,并获得两者石子数之和的价值,求合并成一堆的最小/最大价值。石子数显然是正整数。

  • 数据范围:\(n\leqslant 10^2\)。可以加强到 \(n\leqslant 5\times 10^2\),大概?

  • 这里要求相邻才能合并,否则可以贪心(Huffman 树,合并果子)。

  • 考虑设计 dp:

    • 参看“分裂式问题”。

    • 这里只给出 \(cal\)\(cal(l,r)=sum(l,r)\)

  • 主要问题就是环,暴力枚举断点破环成链即可。复杂度 \(O(n^4)\)

  • 我可以把序列扩展一倍。复杂度 \(O(n^3)\)

  • 其中的最小值部分符合四边形不等式优化的条件,参看四边形不等式优化,可以做 \(O(n^2)\)。最大值...那边也谈了,总之现在就是没有办法。

P1063 [NOIP2006 提高组] 能量项链

  • 题意:

    • \(n\) 个珠子构成一个环,每个珠子有两个参数 \(hd,tl\),保证 \(tl_i=hd_{i+1}\),环意义下的。

    • 每次可以合并相邻两个获得一个新珠子 \(hd_i,tl_{i+1}\)\(hd_i\times tl_i\times tl_{i+1}\) 的价值。

    • 求最大价值。

  • 数据范围:\(n\leqslant 10^2\)

  • 考虑设计 dp:

    • 参看“分裂式问题”。

    • 这里 \(cal(l,r,k)=hd_l\times tl_k\times tl_r\),表示分裂成 \((l,k)\)\((k+1,r)\) 的转移系数。

  • 扩展一倍的话,\(O(n^3)\)

P4170 [CQOI2007] 涂色

  • 题意:

    • 给出数组 \(a_n,c_n\),开始时 \(a_i=0\)

    • 你可以将 \(a_{l\sim r}\) 改为同一个值。

    • 问最少多少次操作后,\(a\)\(c\) 相同。

  • 数据范围:\(n\leqslant 50\)

  • 发现这道题看起来怎么看怎么像覆盖式啊。

  • 然而事实上不是,考虑 \(121343\) 之类的情况,会发现每次只从两头切显然是错的:你不能复用不代表你的子段不能复用。

  • 照旧设计 dp:

    • 状态设计:平凡。

    • 初始化:\(dp_{l,l}=1\)

    • 状态转移方程:

      • 首先是老一套的 \(dp_{l,r}=\min_{k=l}^{r-1}(dp_{l,k}+dp_{k+1,r})\)

      • 然后是 \(c_l=c_r\) 时的特殊转移:\(dp_{l,r}=dp_{l+1,r}=dp_{l,r-1}\)

      • 当这个条件成立的时候,上面那个转移没有必要。

      • 它实际上代表着“早晚要涂左/右边不如多涂一格”。

  • 复杂度 \(O(n^3)\)

P1040 [NOIP2003 提高组] 加分二叉树

  • 除输出方案外,平凡。略。

P4342 [IOI1998] Polygon

  • 有点抖机灵的一道题,当然,这是因为我没想明白。

  • 大体上就是一道不能四边形的石子合并,特色大概是正负变换。

CF1572C Paint

  • 题意:加强版涂色,每次只能将连续且同色的一段改变颜色,求全部变成一个颜色的最小操作次数。

  • 数据范围:\(\sum\limits_{i=1}^T n\leqslant 3000\),初始时每种颜色的格子最多只有 \(20\) 个。

  • 容易想到一个 dp 形如 \(f_{l,r}\) 表示将 \([l,r]\) 全部涂成一个颜色的最小次数和一个 bitset \(g_{l,r}\) 表示恰使用 \(f_{l,r}\) 次能把 \([l,r]\) 涂成啥颜色,转移时枚举分割线,然后 bitset 与一下得到新的 bitset(为空的话令 \(f+1\) 并令 bitset 为 all),\(O(\frac{n^4}{w})\)

  • 注意到这个 dp 的主要问题在于颜色太多了...考虑 \(20\) 的特殊性质,显然数量不一定重要真正重要的是分了几段,于是先将同色段合并。

  • 于是有结论:\(g_{l,r,c_l}=1\)。不妨假设其他颜色的次数更少,则一定得把 \(l\) 处颜色改动,于是反过来先把 \([l+1,r]\) 改成那种颜色,再全部刷成 \(c_l\),不更劣。显然对于 \(r\) 有相同的结论。

  • 唔...很棒的结论。但好像还不够强...考虑一下,怎么转移?

    • 若区间长度为 \(1\),显然到底了。

    • 否则,首先我可以 \(f_{l,r}=f_{l+1,r}+1\)。注意这并不代表涂 \(l\),而是代表递归然后涂 \([l+1,r]\),因为我们规定涂成 \(c_l\)。这一转移不应当有对称转移。

    • 再否则...我不一次把 \([l+1,r]\) 刷成 \(c_l\),也就不用把 \([l+1,r]\) 刷成 \(c_{l+1}\)(这么做可能是不优的)。考虑枚举分割线,应当有 \(f_{l,r}=\min_{k=l}^{r-1} f_{l,k}+f_{k+1,r}+[c_l\neq c_{k+1}]\)

  • 显然这个转移的复杂度还是太高了...考虑什么时候第二种转移才是更优的:\(f_{l+1,r}+1>f_{l,k}+f_{k+1,r}+[c_l\neq c_{k+1}]\)。直觉上来讲,大部分情况下应该有 \(f_{l,r}\leqslant f_{l,k}+f_{k+1,r}(c_l=c_{k+1})\),因为一个大区间给了我们更多决策,而这是分开的小区间所不允许的...或者说,先不递归地考虑这种转移,则上式相当于 \(f_{l+1,r}<f_{l+1,k}+f_{k+1,r}+[c_l\neq c_{k+1}]\),那么这里应该是有 \(f_{l+1,r}\leqslant f_{l+1,k}+f_{k+1,r}\) 的。

  • 唔...大胆猜一个类似贪心的结论:当且仅当 \(c_{k+1}=c_l\),分割有意义。否则,多分一段至少在最后多涂一次,而这一分割能反而少涂一次(从而使得其是最优决策)是很不可思议的。更具体地,从上面的推导可以看出,式右的两项至少要有一项也是用第二种方式转移来的,即这一分割必须为更小区间创造了某种使分割比不分割更优的条件,从而使得这一分割是优的;但我们的 \(f\) 的值都是从 \(+1\)\(+[]\) 来的,所以如果这里式右的 \([]=1\),那似乎还不如直接 \(+1\) 换一个有宽裕决策的大区间。

  • 事实上这是对的。考虑给出一个严谨些的证明:先把 \([l,r]\) 劈成很多个 \([l_i,r_i]\) 的段,满足 \(c_{l_i}=c_l\),且任何一个区间内只有 \(c_{l_i}=c_l\),可以认为这是把区间 DP 的多层转移展开了。此时在任何一个 \([l_i,r_i]\) 中,分割都显然地无意义,因为 \([]\) 一定为 \(1\),而不作用于 \(c_l\) 上的 \([]=0\) 事实上被 \(f_{l_i+1,r_i}\) 所包含。

  • 那么我们看到,这并不是说 \([]=1\) 的分割是不优的;而是说它们是没有必要(被 \(f_{l+1,r}+1\) 的转移和 \([]=0\) 的转移构成的“组合转移”所包含,所代替)的。也许应该称之为“剪除转移优化”,我们在以往似乎也见过但说不出题目。我认为,这与 dp 的运筹学本质是深刻契合的:用有限的决策,描述实际的整个决策过程,唔,也是一种“线性基”?其转移“线性无关”。

  • 另一种不同但更严谨、更妙,思路类似最小路径覆盖的 dp(转自 dottle 的题解):

  • 有一个显然的合并方法,其操作次数为 \(n-1\)

  • 若让两个相同颜色的像素 \(i,j\) 在同一次操作内更改颜色,则答案会减少 \(1\)。要这样做的话,我们相当于需要让 \((i,j)\) 的所有像素变成同一个颜色,再让 \([i,j]\) 的所有像素变为同一个颜色。我们称这样过程为优化了 \((i,j)\)

  • 容易发现,对于 \(a<b<c<d\),我们不能同时优化 \((a,c)\)\((b,d)\)(我的注:这代表着其满足分裂式区间 DP 的分裂性,或者说区间不相关性。事实上,这也许是无后效性的某种特殊体现)。

  • \(f_{i,j}\) 表示合并 \([i,j]\) 的最大优化次数。那么最终的答案就是 \(n-1-f_{i,n}\)。转移方程则是

\[f_{i,j}=\max(f_{i+1,j},\max_{a_i=a_k}f_{i+1,k-1}+1+f_{k,j}) \]

  • 前半部分不赘述,后半部分的含义即我们优化了 \((i,k)\)。注意加号后面是 \(f_{k,j}\) 而非 \(f_{k+1,j}\),原因是我们可以同时优化 \((i,k)\)\((k,j)\),表示这三个像素在同时被更改颜色了。

  • 复杂度 \(O(20n^2)\),区间 DP 的常数不大,能过。

覆盖式问题

  • 典型代表如 \(\text{P1220 关路灯}\)

  • 特点是题目往往要求将一个链/环上的所有点覆盖,每个点被覆盖时会有一个 \(f(t)\) 即与被覆盖时间相关的估价函数,求最大/最小价值。

    • 通常情况下,可操作位置是在序列上不断移动的,换言之只能同时操作一处,且由于操作往往不需要代价,可操作位置在已处理区间的端点。

    • 通常需要 \(f(t)\)同质的,即每个点的估价函数只能有系数的不同,不能有形式的不同,譬如不能一个是一次函数一个是二次函数。

  • 状态通常为 \(dp_{l,r,0/1}\) 表示处理完 \(l\sim r\),当前在 \(l/r\) 的最大/最小损失,即“没赚到一便士就是亏了一便士”。

  • 初始化通常为 \(dp_{s,s}=0\)

  • 这里只给出在 \(l\) 的状态转移方程,在 \(r\) 的可以对称推出:\(dp_{l,r,0}=\max/\min(dp_{l+1,r,0}+cal(rest,l,l+1),dp_{l+1,r,1}+cal(rest,l,r))\)

    • 其中 \(cal(rest,l,r)\) 是基于尚未覆盖的点的 \(f\)(的系数)和从 \(l\)\(r\) 所用时间来计算损失的函数。
  • 通常复杂度为 \(O(n^2)\)

P1220 关路灯

  • 题意:

    • \(n\) 盏路灯排成一溜,各有位置 \(k_i\) 和功率 \(w_i\),初始时老张在 \(s\),初始时间为 \(0\)

    • 每秒老张可以移动 \(1\) 的距离,关灯时间忽略不计,如果第 \(i\) 盏灯在第 \(j\) 秒被关上,那么它会浪费 \(j\times w_i\) 的电。

    • 求最小的总浪费量。

  • 数据范围:\(n\leqslant 50\)。应该可以做 \(n\leqslant 10^4\)

  • 考虑设计 DP:

    • 参看“覆盖式问题”。

    • 这里只给出 \(cal\)\(cal(rest,l,r)=(k_r-k_l) \sum\limits_{i\in rest} w_i\)

  • 复杂度 \(O(n^2)\)

P2466 [SDOI2008] Sue 的小球

  • 略,只是 P1220 的数据范围放大版罢了。

P4870 [BalticOI 2009 Day1] 甲虫

  • 题意:

    • \(n\) 滴价值为 \(m\) 的水滴在数轴上。初始时甲虫在 \(0\)

    • 每秒甲虫可以移动 \(1\) 的距离,喝水时间忽略不计,如果某个水珠在第 \(t\) 秒被喝到,那么它会贡献 \(\max(0,m-t)\) 的价值。

    • 求最大总价值。

  • 数据范围:\(n\leqslant 3\times 10^2\)

  • 这里的特殊之处主要在于,不一定要都喝,或者说不会有负贡献。之前的“亏损”算法会出问题。

  • 解决办法:提高复杂度!暴力枚举喝哪个区间的所有水滴,然后覆盖式。

  • 乍一看是 \(O(n^4)\),实则不然。枚举了喝的水滴量之后,该轮的所有 dp 复杂度不超过求解 \(dp_{1,n}\) 所需,即不超过 \(\Theta(n^2)\),故总复杂度为 \(O(n^3)\)

P1005 [NOIP2007 提高组] 矩阵取数游戏

  • 除高精外平凡。略。

P6879 [JOI 2020 Final] スタンプラリー 3

  • 抖机灵题。把答案统计放到 dp 的维度上,然后把时间扔到等于号右边,顺推比较好些,其余略。

挂载式问题

  • 典型代表如 \(\text{P2135 方块消除}\)

  • 特点是给出一个本质为链表的序列,即消去某段之后其两边的段会拼到一起,基于段计算价值。

  • 状态通常为 \(dp_{l,r,k}\) 表示处理完 \(l\sim r+k\) 这一段的最小/最大价值。有时为了节约状态,会要求挂载部分和 \(r\) 有某种同质性。

  • 初始化为 \(dp_{l,l,k}=cal(l,k)\),其中 \(cal\) 是基于段信息计算价值的函数。

  • 状态转移方程通常为 \(dp_{l,r,k}=\max/\min(dp_{l,r-1,0}+cal(r,k),dp_{l,key,k+1}+dp_{key+1,r-1,0})\),其中 \(key\in [l,r-1]\)\(c_{key}=c_r\)

    • 实际意义即把后面那一段消掉或者处理一段以设法拼一个更长的。

    • 通过挂载,解决了序列不断移动拼接的问题。

    • 这里给出的一般性 dp 设计很大程度上受了方块消除的影响。事实上,\(calc\) 和挂载方式应该是随题而变的。

  • 注意,这整个 dp 也可以对称设计(即挂载在 \(l\) 左边)。也有时客观上需要两边都挂,那就辅助维比较多。

  • 通常复杂度为 \(O(n^3k)\)\(k\) 为后面挂着的那个辅助维的大小,有时我们不关心挂了多长的只关心有没有,那就是 \(n^3\)(参看 \(\text{10.25 T2 石头剪刀布}\) 我的愚蠢区间 DP,那个不打算做这种 record 了)。

P2135 方块消除

  • 题意:

    • 给出 \(c_n\),每次可以选择一段 \(c\) 全同的 \(l\sim r\) 将其消除并获得 \(len^2\) 的价值,之后其消失,其左右的连接到一起。

    • 求最大总价值。

  • 数据范围:\(n\leqslant 10^3\),但初始时不同的颜色段数量 \(m\leqslant 50\)

  • 考虑设计 dp:

    • 参看“挂载式问题”。

    • 这里只给出 \(cal\)\(cal(l,r)=(r-l+1)^2\)

  • 理论复杂度 \(O(n^4)\),实际上大概在 \(O(m^3n)\) 不到的级别,因为无法跑满,总之是跑得飞快。

  • 注意到这里我们要求 \(c_{key}=c_r\) 才允许拼接,事实上,这是因为“不造成更长同色段的拼接操作不必要”。即可以认为是把 \(c\) 不等时的转移省略掉了,因为其在此题下无意义。

  • 另外有重题,\(\text{UVA10559 方块消除 Blocks}\)。事实上这道题里面 \(n=200\),没保证颜色段数量,理论 \(O(n^4)\),且 \(T=15\),一点机会都没有;但实际上连续多层跑满是不可能的,换言之最后一维不连续,即使连续也不层叠,只依赖其他 \(k=0\) 的 dp,故实际上表现应该在 \(O(n^3)\) 左右。

P5336 [THUSC2016] 成绩单

  • 题意:给出 \(w_n\),每次可以选相连的一段消掉并付出 \(A+B(\max-\min)^2\) 的代价,其中 \(A,B\) 为常系数。求最小总代价。

  • 数据范围:\(n\leqslant 50\)

  • 显然离散化,然后设计 dp 如下:

    • 状态设计:\(dp_{l,r,mn,mx}\) 表示后面挂了 \(mn,mx\) 的一段,把 \(l\sim r\) 及挂载段消完的最小总代价。显然,\(mn,mx\) 就可以代表一段了。

    • 初始化:这里我们采用了比较特殊的方式。方块消除这里的挂载量可以为 \(0\),但这里如果想表示“没有挂载”则需要额外的辅助维,故我们考虑从 \(dp_{1,n-1,w_n,w_n}\) 开始记搜,于是初始化主要是对 \(l>r\) 的情况做,即 \(dp_{l,r(l>r),mn,mx}=calc(mn,mx)\)

    • 状态转移:

      • \(dp_{l,r,mn,mx}=\min \begin{cases}dp_{l,r-1,w_r,w_r}+calc(mn,mx) \\ dp_{l,r-1,\min(mn,w_r),\max(mn,w_r)}\\ dp_{l,k-1(k>l),mn,mx}+dp_{k,r-1,w_r,w_r}\end{cases}\)
    • 分别对应直接消,拼,消一段(\(k\sim r\))以便于拼。这里要求 \(k>l\) 是因为 \(k=l\) 时消没了,拼啥呀,等价于第一种转移了。

  • 复杂度 \(O(n^5)\),足够通过本题。

连续段式问题

  • 典型代表如 \(\text{LOJ #3569.「COCI 2021.11」磁铁}\) 等。

  • 特点是可以在序列的任意位置放置一些物品,物品间如果离得太近就会有特殊效果或特别限制,通常是计数题因为非计数题有时可以设法用一般区间 DP 来处理。为了避免或运用特殊效果/符合特别限制,我们可以把多个物品拼成长度 \(\geqslant\) 某个值的连续段。有些时候,我们并不在意观念中不同的连续段是否相连。

  • 状态设计通常基于“维护一系列距离未知的连续段和它们的总长度(或者还剩的总长度)”,譬如 \(dp_{i,j,k}\) 表示放了 \(i\) 个,当前有 \(j\) 个段,还剩 \(k\) 长的空位。

  • 初始化通常为(沿用上面的状态)\(dp_{0,0,m}=1\),这里 \(m\) 为序列总长度。

  • 状态转移通常形如“建新连续段(后文称建新段),挂载在已有连续段上(后文称扩张段/段扩增)或合并已有连续段(后文称段合并)”,形式化地,

\[dp_{i,j,k}\to \begin{cases} dp_{i+1,j+1,k-1} & k\geqslant 1 \\ dp_{i+1,j,k-(l_{i+1}+1)} & k\geqslant l_{i+1}+1 \\ dp_{i+1,j-1,k-(2l_{i+1}+1)} & k\geqslant 2l_{i+1}+1 \end{cases} \]

  • 这里我们以不许冲突为例,\(l\) 是冲突半径。使用了类似沈阳大街的 trick,预先将物品按冲突半径升序排序,于是后来者决定冲突关系到底占用了多长的空序列。

  • 必须指出的是,维护的一系列连续段可以是有或无相对顺序的。目前看来,两者除了系数差异并没有本质区别;但也存在某些题目几乎强制需求其中某一种实现方式,另一种实现方式非常不自然,我是指细节多到恶心,例如 \(\text{P5999 [CEOI2016] kangaroo}\) 等。往往有相对顺序的是较优越的。

  • 关于段合并:有些时候,合并操作是必须的,因为只有通过合并,\(dp\) 过程所对应的实际过程才确定下来,例如磁铁中无相对顺序 dp 的情况。作为对比,有相对顺序的设计本身是一个更确定(但未必足够确定)的过程,合并操作可能是不必要的。另外,单合并和段扩增常常是等价的概念。

  • 一定程度上,其和挂载式问题有几分相像。不过挂载式是将正常区间 DP 无法处理的非区间规约化,然后挂在后面,相当于是给一般的区间 DP 打补丁;而连续段式 dp 则直接舍弃了“维护一个区间”的思路,转而维护在序列上的长度确定、位置未知的连续段,按实际过程的顺序来转移。

  • 可以说这种“不确定在哪、不确定间隔”的连续段是一种将问题模糊化以便于计数的手段。事实上,这一模糊化也要求我们准确把握题目,以使得我们的 dp 过程和实际过程一一对应。

  • 通常复杂度为 \(O(n^2m)\)\(n\) 为物品数,\(m\) 为序列长度。特别地,如果物品数和序列长度一样那么没有 \(m\),因为容易算出还剩多长的空序列。

LOJ #3569.「COCI 2021.11」磁铁

  • 题意:给定 \(n\) 个冲突半径为 \(l_{1\sim n}\) 的磁铁,求将它们放到一个长为 \(m\) 的序列上且不冲突的方案数。

  • 数据范围:\(n\leqslant 50,m\leqslant 10^4\)。保证 \(n\leqslant m\)

  • 显然无从下手,容易想到一个线性状压状态为 \(dp_{sta,i}\) 表示放了 \(sta\) 中的磁铁,占用了 \(1\sim i\) 的方案数,但非常不乐观。

  • 考虑我们为什么要状压。首先是因为磁铁可能以任意排列被顺序放置,然后是两个磁铁之间的冲突到底占用多少空序列和两者都有关。

  • 模仿沈阳大街,将磁铁按冲突半径不降排序,于是冲突只和较晚的一者有关。然而状压下这是无意义的...无论如何得把状压打掉。

  • 那么就有一个惊为天人的 dp 设计,参看“连续段式问题”处。

    • 这里我们采用不考虑连续段相对顺序的实现。

    • 这代表着,开新段的转移系数为 \(1\),扩张是 \(2j\),合并是 \(\binom{j}{2}\)

  • 最后的问题是,我们不知道连续段和空格的相对位置。

  • 注意到 \(dp_{n,1}\) 本质上可以看做一个构建一棵确定的笛卡尔树的过程,而笛卡尔树和磁铁的排列一一对应,故我们只取只剩一段的所有 dp,即 \(dp_{n,1}\) 来考虑,然后把空格问题计算进去即可。对于这些 dp,一共有 \(n\) 个磁铁,也即 \(n+1\) 个间隔可以放空格,显然系数即为 \(\binom{n+k}{k}\)

  • 另一种考虑连续段相对顺序的实现:开新段 \(j+1\),扩张 \(2j\),合并 \(j-1\)

  • 可以证明,这样 dp 出的 \(dp_{n,1}\) 也和笛卡尔树一一对应,然后插空格就好。为什么上面的方式不考虑顺序,却还是确定的(而非子树间无序)的笛卡尔树?因为合并过程为它赋予了顺序。实践也表明两者是等价的。

  • 复杂度 \(O(n^2m)\)

CF1515E Phoenix and Computers

  • 题意:求将 \(1\sim n\) 的灯打开的方案数。如果 \(i-1,i+1\) 都被打开,那么 \(i\) 会自动打开,不能再被手动打开。两个方案不同,当且仅当打开的灯不同,或打开的顺序不同。

  • 数据范围:\(n\leqslant 400\),其实应该可以做 \(n\leqslant 10^4\)

  • 本题的一个特点是最后会全部开完。由此,设计一个考虑相对顺序的 dp 如下:

    • 状态设计:\(dp_{i,j}\) 表示一共开了 \(i\) 个灯,当前有 \(j\) 个连续段的方案数。

    • 初始化:\(dp_{1,1}=1\)。这是有原因的。

    • 状态转移:

      • 开新段:只考虑相对位置,\(dp_{i,j}\to dp_{i+2,j+1}\),系数为 \(j+1\)。这里我们把会被自动开的那个灯“预支”了,这也是为什么我们要从 \(1\) 开始初始化。

      • 段扩增:\(dp_{i,j}\to dp_{i+1,j}\),系数为 \(2j\)。显然。

  • \(ans=\sum\limits_{i=1}^{\lceil\frac{n}{2}\rceil} dp_{n,i}\)。任何一个实际过程都可以和我们一一对应,具体地,可以认为连续段之间总是保持 \(1\) 的距离,开新段时强行插入一个 ._ 在原有的两个 .___ 之间,挂载时将从被挂载的段的 ___ 部分开始的点坐标全部 \(+1\),将空出来的点变成 _,感性理解上这对实际过程构成双射。

  • 其他考虑相对顺序的 dp 设计:

    • \(dp_{i,j}\) 表示手动开了 \(i\) 个分 \(j\) 段的就不谈了,这是同构状态。

    • 考虑加上合并操作,按中间的间隔长度,可以分为单合并(新创建的单元素段和已有段合并),双合并和三合并三种。于是 \(ans=dp_{n,1}\)

    • 应当指出的是,这里预支自动灯恰恰是将序列“确定”下来的关键。如果不预支自动灯,那么合并将是必要的,在合并时根据单、双和三计算自动灯影响。

  • 不考虑相对顺序的 dp 设计:...好像也是平凡的。

    • 简单更改系数,然后 \(dp_{n,1}\) 即为答案。

    • 应当指出的是,这里的合并系数(我指的是双合并和三合并,毕竟单合并本质是挂载)不是 \(\binom{j}{2}\) 而应该是 \(A(j,2)\),因为两者谁在前谁在后是不同的方案,即使被合并的两段内部的操作顺序完全相同,对应的实际操作序列也不同。

    • 不妨举个例子看看:令 \(n=4\),则 143413 是本质不同的两种方案,因其操作序列不同。在我们的 dp 中,其实是“早开段在前和晚开段在前不同”,注意这里的早晚并不是真正的早晚,只是指出两者在序列上的前后对操作在时间上的前后有影响。简单地,设 x 为早开段的第一手的时间而 y 为晚开段的第一手的时间,则对应的序列被操作时间分别为 x...merge...y...y...merge...x

  • 复杂度 \(O(n^2)\)。事实上,我们能看出,许多类似但不同的 dp 设计都是对的,只要满足一个条件:正确地刻画了实际过程,即与实际过程一一对应。

  • 别走。我们考虑一个问题:为什么磁铁的 \(dp\) 是三维的,而本题是两维的?更进一步地,如果本题推广到更长的序列但只要求手动开够 \(m\) 个灯,若手动开的灯的集合不同或开的顺序不同则认为是不同的方案,该怎么做?

  • 第一问:因为磁铁这道题到最后我们要考虑空位和放下的磁铁的相对位置关系,需要 \(C\) 出对应的系数,剩余的不同空位数也对应着不同的系数;而在本题中,最后所有方案都剩 \(0\) 个空位,自然系数都是 \(1\)

  • 第二问:简单起见(毕竟本题显然有很多实现方式),我们采用一个时间复杂度较优且不用考虑避免算重的 dp。考虑设计 \(dp_{i,j}\) 表示开 \(i\)\(j\) 段,转移上我们不预支自动灯,不启用合并,这代表着我们其实所有自动灯都没开(避免算重),然后最后的系数变成一个方程 \(x_1+x_2+\dots+x_{j+1}=n-m\),其中 \(x_{2\sim j}\)\(\geqslant 1\)\(x_1,x_{j+1}\geqslant 0\),显然这是经典的组合数问题,\(O(m^2)\) 得解,且也许可以对 \(n\) 插值。

  • bouns:为什么磁铁不能这么做?——因为磁铁的元素是极不同的,其在不同的排列下的表现是更加极不同的。

  • extra:我想到了一个 \(O(m\times \operatorname{getinv})\) 的做法,这代表着可以做 \(n=10^{18},m=6\times 10^5\) 或者 \(n=m=10^8\)。因为这实在是太有趣了,让它暂时停留在我的脑子里。

P5999 [CEOI2016] kangaroo

  • 题意:求满足 \(p_1=s,p_n=t\),且峰谷交替(即每个点都是相邻三者中的极大值或极小值,不对 \(p_1,p_n\) 做要求)的 \(1\sim n\) 的排列数。

  • 数据范围:\(n\leqslant 2\times 10^3\)。很怪的数据范围哦。

  • 容易想到一个沈阳大街:我们按 \(1\sim n\) 的顺序把这些数字填到 \(p\) 的某些位上。

  • 显然一般区间 DP 的 \(dp_{l,r}\)...无论如何得枚举一个断点吧,比较优的方式是枚举最小值位置,但显然还不够,总之是没戏的方向,老生常谈之操作序列合不到一起去。

  • 转而考虑使用连续段式区间 DP:

    • 状态设计:\(dp_{i,j}\) 表示放完 \(1\sim i\),有 \(j\) 个连续段的方案数。我们采用考虑相对顺序的。

    • 初始化:\(dp_{0,0}=1\)

    • 状态转移:

      • 开新段:\(dp_{i,j}\to dp_{i+1,j+1}\),系数为 \(j+1\)

      • 段扩增:......不可做。容易看出只有长为 \(1,2\) 的段才可以延长,但分别维护数量就炸了。

  • ???那怎么办?

  • 回想我们之前说的:合并是一个不必要的操作。然而以普遍理性而论,扩张段应该是可以视为某种形式上的单合并的...并且,在我们不合并的时候,最后段其实还是连了起来。任意段数对答案贡献。

  • 嘶...是不是说,平时,合并的操作可以被扩张的(一个或多个)段代替;而反过来...我们可以尝试用合并代替扩张。

  • 发现双合并之后的段仍然是可合并的——我是指,仍然满足边界是谷。于是舍弃扩张段操作,改用双合并段:\(dp_{i,j}\to dp_{i+1,j-1}\),系数为 \(j-1\)

  • 注意 \(s,t\) 不论是开新段还是合并,系数都为 \(1\),且应该是单合并;注意,其他元素单合并是不合法的,因为会导致两个插头变成一个,只有双合并才不改变段性质(也正因此我们可以不要扩张操作),而 \(s,t\) 对于段性质的改变容易直接算出。

  • 事实上这一操作可以推广,即开辅助维记录当前是否已经钦定了起始段/结束段。可以从中看出,想要使用不考虑相对顺序的实现的话,具体细节会很多,因为统计插空方案的时候会非常复杂,需要考虑强制定位的首段和尾段的影响。

  • 最后 \(dp_{n,1}\) 即为答案,其他的虽然也连了起来但可能段交界处不满足条件。复杂度 \(O(n^2)\)

LOJ #2743.「JOI Open 2016」摩天大楼

  • 题意:给出一个序列 \(a_{1\sim n}\),求满足 \(\sum\limits_{i=1}^{n-1} |a_{p_i}-a_{p_{i+1}}|\leqslant m\) 的排列 \(p\) 有多少种。

  • 数据范围:\(n\leqslant 10^2,m,a\leqslant 10^3\)

  • 容易想到连续段式 dp,但发现不可能记录每个连续段的左右端点是多大,会退化成暴力模拟。

  • 注意到我们肯定要合并,不合并是没法统计答案的。

  • 仍然按不降序填入每个数字,于是可以拆绝对值括号,故考虑设计如下 dp(首尾段相关的系数细节略去):

    • 状态设计:\(dp_{i,j,k}\) 表示放了前 \(i\) 个,分 \(j\) 段,当前该式的和为 \(k\)(不考虑未放入的数字的影响)的方案数。

    • 初始化:\(dp_{0,0,0}=1\)

    • 状态转移:

      • 开新段:\(dp_{i,j,k}\to dp_{i+1,j+1,k-2a_{i+1}}\),转移系数为 \(j+1\)

      • 段扩增:\(dp_{i,j,k}\to dp_{i+1,j+1,k}\),转移系数为 \(2j\)。之所以 \(k\) 不变是因为现在它做前项,以后它还要做后项。

      • 段合并:\(dp_{i,j,k}\to dp_{i+1,j-1,k+2a_{i+1}}\),转移系数为 \(j-1\)

  • 先不谈首尾段细节相关,我们容易发现一件事:第三维炸了。最终结果 \(\leqslant m\),但中间过程可能是天马行空的。

  • ...(炎国粗口),是的。去网上贺别人的题解,并且因为理解能力低下只看懂第一步之后转而考虑直接维护一个“期望的 \(k\)”,即 \(k\) 在可预期的未来至少是多少,因为随着插入的过程,期望和显然是单调不降的,我们可以考虑把段边上的空位放一个虚拟的数,其在数值上和段边界的数值相等,从而获得一种实际最终 \(k\) 的不精确刻画。

  • 很 heuristic——这个词好像和中文里的“启发性的”有语义偏差,不要管它——的方向,然而这使得我们完全无法转移了,开历史倒车到维护段边界的值那里...还不够,还差至少一块拼图。

  • 考虑换一个方向化式子,既然我们的 \(a\) 是不降的,那么如果 \(a_i,a_j\) 相邻且 \(i>j\)(这里的 \(a\) 是升序排序后的),那么其贡献为 \(a_i-a_j=a_i-a_{i-1}+a_{i-1}-a_{i-2}+\dots-a_j\)...模仿纪念品那道题!将贡献分步计算!

  • 遂设计 dp 如下:

    • 状态设计:\(dp_{i,j,k,0\sim 2}\) 表示当前放完 \(a_1\sim a_i\),分 \(j\) 段(考虑相对顺序),有 \(0\sim 2\) 段是边界段,当前估价为 \(k\) 的方案数。

    • 初始化:\(dp_{0,0,0,0}=1\)

    • 状态转移:

      • 开新段:对于常规段,\(dp_{i,j,k,l}\to tp_{j+1,k,l}\),转移系数为 \(j+1-l\);对于边界段,\(dp_{i,j,k,l}\to tp_{j+1,k,l+1}\),转移系数为 \(2-l\)

      • 段扩增:\(dp_{i,j,k,l}\to tp_{j,k-(a_{i+1}-a_i),l}\),转移系数为 \(2j-l\)

      • 段合并:\(dp_{i,j,k,l}\to tp_{j-1,k,l}\),转移系数为 \(j-1\),显然这里有二合并就够了。

      • 和维护:上面的 dp 本质上是不断地在已有段的旁边放虚拟的 \(a_i\),故当 \(i\to i+1\)\(dp_{i+1,j,k,l}=tp_{j,k-(2j-l)(a_{i+1}-a_i),l}\)。注意,这一转移本质上是错误的,因为扩增的时候可能会临时访问到负下标,这里的写法只是示意。

  • 实践表明,这一 dp 是扯淡。回想我们为什么要规定边界段:确定当前有多少个插头。由此,导致了对系数的影响。然而不妨考虑只有一个段,通过不断左右扩增达到满的情况:上面这一 dp 是无法描述它的,即使将初始化改为 \(dp_{1,1,1,0}=dp_{1,1,2,0}=1,dp_{1,1,1,0}=2\),仍然会发现在对 \(dp_{1,1,2,0}\) 的扩增过程中 \(k\) 根本不变。

  • 故边界段和固定的边界(不再允许在那边建新段、扩增)是不同的概念,我们应该基于后者来设计 dp,如下:

    • 状态设计:\(dp_{i,j,k,l}\) 表示放完 \(a_{1\sim i}\),当前有 \(j\) 个固定的边界,当前有 \(k\) 个段,当前估价为 \(l\) 的方案数(在写上面那个错 dp 的实际实践中,发现把 \(l\) 换到 \(k\),不,\(j\) 前面,是连续性较好且较自然的维度顺序)。

    • 初始化:\(dp_{1,0,1,0}=1,dp_{1,1,1,0}=2,dp_{1,2,1,0}=1\)

    • 状态转移:

      • 定义函数 \(calc(i,j,k,l,t)=l+(2k-j-t)(a_i-a{i-1})\),其是一个快速计算转移到的目标估价的函数,最后一个参为“有多少个插头是新建(多算)的”。

      • 开新段:

        • 普通段:\(dp_{i,j,k,l}\to dp_{i+1,j,k+1,calc(i+1,j,k+1,l,2)}\),转移系数为 \(k-1\)

        • 首尾段:不固定边界,\(dp_{i,j,k,l}\to dp_{i+1,j,k+1,calc(i+1,j,k+1,l,2)}\),转移系数为 \(2-j\)(因不能在越过固定边界的地方开新段),实现中和普通段合并就好;固定边界,\(dp_{i,j,k,l}\to dp_{i,j+1,k+1,calc(i+1,j+1,k+1,l,1)}\),转移系数为 \(2-j\)

      • 扩增段:

        • 非边界:\(dp_{i,j,k,l}\to dp_{i+1,j,k,calc(i+1,j,k,l,0)}\),转移系数为 \(2k-2\)

        • 边界:不固定边界,\(dp_{i,j,k,l}\to dp_{i+1,j,k,calc(i+1,j,k,l,0)}\),转移系数为 \(2-j\),实现同上;固定边界,\(dp_{i,j,k,l}\to dp_{i+1,j+1,k,calc(i+1,j+1,k,l,-1)}\),转移系数为 \(2-j\)

      • 合并段:\(dp_{i,j,k,l}\to dp_{i+1,j,k-1,calc(i+1,j,k-1,-2)}\),转移系数为 \(k-1\)

  • 时间复杂度显然为 \(O(n^2m)\)。本题的不考虑相对顺序也很恶心,故略。后面除非需要,不再谈这种实现。

  • 本题也是个细节地狱。你可以用本题对你的连续段 dp 的理解深刻程度做一个小测试。

CF704B Ant Man

  • 题意略,因为太烦了。有了上一道题的基础,我们可以比较得心应手地做这道题了。

  • 首先还是典中典的升序填入。发现左右插头是要区分讨论的...无论如何先试水地设计一个 dp:

    • 状态设计:\(dp_{i,j}\) 表示填入了 \(1\sim i\),分 \(j\) 段,的最小总代价。

    • 初始化:\(dp_{0,0}=0\)

    • 状态转移:

      • 开新段:\(dp_{i,j}\to dp_{i+1,j+1}\),转移系数为...嗯,显然我们是要合并的,那么 \(i+1\) 一定会作为 \(i>j\)\(j\) 一次,作为 \(i<j\)\(i\) 一次。将其影响求和,为 \(-2x_{i+1}+b_{i+1}+d_{i+1}\)。类似袋鼠那道题的,\(s,e\) 有着特殊的转移,其只有一边。

      • 段扩增:\(dp_{i,j}\to dp_{i+1,j}\),唔...若左插头存在,则其可以作为 \(i>j\)\(i\)\(j\),系数可以为 \(b_{i+1}+c_{i+1}\);若右插头存在,则其可以作为 \(i<j\)\(i\)\(j\),系数可以为 \(a_{i+1}+d_{i+1}\)

      • 段合并:\(dp_{i,j}\to dp_{i+1,j-1}\),需要左右插头都存在,其同时作为 \(i>j\)\(i\)\(i<j\)\(j\),系数为 \(2x_{i+1}+a_{i+1}+c_{i+1}\)

  • 唔,总体上讲,不是一个很 challenging 的题目。细节在于计算左右插头的存在性,显然其是只和 \(i,j\) 相关的,还有 \(s,t\) 相关的特殊转移。\(O(n^2)\)

P2612 [ZJOI2012] 波浪

  • 题意:把摩天大楼的序列换成排列,求权值 \(\geqslant m\) 的排列的占比,输出一个 \(K\) 位精度的实数。

  • 数据范围:\(n\leqslant 100,K\leqslant 30,m\) 在 int 范围内。

  • 首先容易意识到权值最大的排列为 \(1,100,2,99,3,98,\dots\) 或者差不多的东西,总之有一个很松的权值上界 \(n^2\),所以容易做一个求方案数的 dp。

  • 然而没办法除 \(100!\),这是一个 \(153\) 位的数字,ld 也存不下,__float128 也存不下。解决办法:将 dp 定义中的方案数改为概率,即每放一个 \(i\),所有方案数 \(/i\)

  • 剩余部分参考摩天大楼。如果精度还是不够可以模拟小数类(类似高精度),但事实上这里精度和常数都卡得很紧,我的实现参考了题解的办法:按精度要求数据点分治。复杂度 \(O(n^4)\)

CF GYM102538H Horrible Cycles

  • 题意:给出一张左右部点数均为 \(n\) 的二分图,其中左部第 \(i\) 个点和右部第 \(1\sim a_i\) 个点有边,求本质不同的简单环个数。两个简单环本质不同当且仅当其边集不同,一个环简单当且仅当其不经过任意点超过一次。

  • 数据范围:\(n\leqslant 5\times 10^3\)

  • 毫无思路对吧?我也是被告知这道题是连续段之后来做的...但还是毫无思路。

  • 不论如何,考察一下二分图上的环是什么样子的:一条交错路+一条把首尾连起来的边。嗯...

  • 考虑到连续段式有一个非常典的手法就是顺着偏序走,不妨按 \(a\) 升序排序(毕竟编号是无意义的)来做。考察最简单的情况,即已经有一个点(右部点初始时就全都在,我们是一点点加左部点),然后再加一个点进去,会构成多少个环?

  • 此时只会有四元环,应有 \(\binom{\min(a_1,a_2)}{2}\) 个。如果再加一个点呢?

  • 啊...推广之后好像是已经加进去的点的所有子集和当前点的 \(W\) 交起来然后 \(C\) 出来对应个点...形式化地,\(\sum\limits_{s\in S} \binom{W(s)\cap W(now)}{|s|+1}=\sum\limits_{W(s)} \sum\limits_{|s|} \binom{W(s)}{|s|+1}\times cnt(s)\)

  • 假的,或者说 \(W(s)\) 不可求:\(W(s)\) 不等于并集,这里也不能简单地把 \(W(s)\)\(W(now)\) 并起来,理由显然。故自然地想到维护交错路径,并以此设计 dp,显然交错路径也是某种意义上的连续段,于是:

    • 又一个假做法:\(dp_{i,j}\) 表示考虑完前 \(i\) 个点,有 \(j\) 条交错路径的方案数。这个东西不可转移,我们不知道右部有多少点是空余的。

    • 好了来说真做法。模仿我们在 kangaroo 和沈阳大街中的操作,首先将左右部的点合并起来构成一个序列,保证每个左部点加入时,已加入的右部点恰为 \(1\sim a_i\),接着让右部点参与 dp 以此避免需要从右部取空点的合并操作。具体见下。

    • 状态设计:\(dp_{i,j}\) 表示考虑了该序列的前 \(i\) 个点,当前有 \(j\) 条交错路的方案数。注意交错路是关于点不交的。注意到交错路合并时会有端口问题(正常交错路是两个端口,但单点是一个),我们强制规定交错路是有向的,最后除二即可。注意,单点的交错路是无向的,因为向在边上,是在合并的时候才决定向,准确地说合并时决定哪个交错路负责出,哪个负责入,可以认为跟端口并没有什么关系(把端口的系数变成了定向的系数)。

    • 初始化:\(dp_{0,0}=1\)

    • 状态转移:

      • \(i+1\) 为右部点:

        • 不要它,\(dp_{i,j}\to dp_{i+1,j}\),转移系数为 \(1\)

        • 新建交错路,\(dp_{i,j}\to dp_{i+1,j+1}\),转移系数为 \(1\)

      • \(i+1\) 为左部点:

        • 不要它,\(dp_{i,j}\to dp_{i+1,j}\),转移系数为 \(1\)

        • 交错路合并,\(dp_{i,j}\to dp_{i+1,j-1}\),转移系数为 \(2\binom{j}{2}\),这里的 \(2\) 是决定第一个交错路负责出还是入(从而第二个的选择是唯一的);

        • 交错路结算(将首尾相连,变为环),\(dp_{i,1}\to ans\),转移系数为 \(1\),注意之所以从 \(dp_{i,1}\) 出发来转移就是为了避免算重。注意到会把一条边也视为一个环,注意单点无向所以只会算一遍,记得减去。

  • 复杂度 \(O(n^2)\)。比较独特且精妙的一道连续段式...另外,我手生了啊。

23.2.20 T4 错排问题

  • 题意:对 \(\sum\limits_{i=1}^n |i-p_i|=k\in [0,m]\) 的错排 \(p_{1\sim n}\) 分别计数。

  • 数据范围:\(n\leqslant 10^3,m\leqslant 2\times 10^3\)

  • 震撼我...事实证明连续段式 dp 不仅和区间没啥关系,甚至和连续段没啥关系...也许该叫端口式 dp?

  • 注意到错排有着某种置换环本质。不要在意是不是环,我想说的是:\(\sum\limits_{i=1}^n |i-p_i|=\sum\limits_{i=1}^n |pos_i-i|\)

  • 故考虑设计如下 dp:

    • 状态设计:\(f_{i,j,k}\) 表示考虑了前 \(i\) 个点,有 \(j\) 个空(也即有 \(j\) 个多余的数),目前和为 \(k\) 的方案数。

    • 初始化:\(f_{0,0,0}=1\)

    • 状态转移:需要同时处理新增点和新增空,分别说吧。

      • 对于新增点:可以和前面的空匹配,使 \(j-1\),本身对 \(k\) 造成正贡献;可以等着,对 \(k\) 造成负贡献。

      • 对于新增空:可以和前面的数匹配,使 \(j-1\),本身对 \(k\) 造成正贡献;可以等着,对 \(k\) 造成负贡献。

  • 显然这个 dp 有些本质的问题,主要是第三维太大了,过程中的和会暴涨到 \(O(n^2)\) 级别...这好像挺典的啊。

  • 设法将绝对值中的两者捆绑计算代价,即不作差而是直接对差 dp,将直接计算贡献改为前面留着的点和空每经过一个 \(i\) 而没有被处理掉(包括其本身,左闭右开)就对 \(k\) 造成 \(1\) 的贡献即可,于是第三维压到了 \(O(m)\)

  • 还是不太行。注意到 \(i\) 每增加 \(1\)\(k+=2j\),且 \(j\) 每次最多 \(-1\),故若现在 \(j=x\),则 \(k\) 至少要加 \(2x+(2x-2)+(2x-4)+...=\frac{(2x+2)x}{2}>x^2\),换言之 \(j\geqslant \sqrt{m}\) 是必然会炸的。好了,状态数 \(O(nm^\frac{3}{2})\),转移认为是 \(O(1)\) 算了,结束。

笛卡尔树式问题

  • 我怀疑它的名字早晚被连续段抢了去...

  • 典型代表...我觉得 \(\text{P3592 [POI2015] MYJ}\)\(\text{P5851 [USACO19DEC] Greedy Pie Eaters P}\) 都挺典型的,不过是不同的典型。

  • 我们先来谈谈笛卡尔树:

    • 笛卡尔树是一种...相比于“一种数据结构”的 OI Wiki 定义,我认为它更像一种数学模型(即使真的建笛卡尔树,我们也是把它当做一棵图论意义下的树,而非数据结构意义下的树来使用)。

    • 笛卡尔树上的点有两个值 \((k,w)\)。在 \(k\) 意义下,笛卡尔树是二叉搜索树;在 \(w\) 意义下,笛卡尔树是堆。

    • 很像 treap?那就对了。不过我们这里并不是把它作为数据结构来使用。

    • 实际使用中,我们一般把数组下标作为 \(k\)。此时其有着美妙的性质:

      • 任意子树都对应着原序列的连续段。

      • 每个子树的根都是区间最值,并把区间进一步划分为两部分。

    • 这有什么意义呢?嗯...

      • 基于序列可能无法设计的状态,在树上显然好一些——至少我不觉得 tdp 的状态难以设计(和线性之类的比起来)。设计出了状态就万事大吉嘛。

      • 同时,树形具有明显的阶段性和划分性,或者说递归性质?相对来讲比较舒服。

      • 典型代表如 \(\text{P7219 [JOISC2020] 星座 3}\),当然这是个笛卡尔树重构图(大概?或者该说题意转化?)+线合优化 DP,和我们这里的区间 DP 就没啥关系了。

  • 特点是给出很多序列上的区间,每个区间都对序列上的点有一定需求,满足不同的需求有不同的收益,求最大收益。

  • 状态通常为 \(dp_{l,r}\) 表示把 \(l,r\) 这段定下来的最大收益。可能有辅助维,当然啦,这是因题而变的。

  • 初始化通常为...没有初始化。我们在转移方程那里解释。

  • 状态转移方程通常为 \(dp_{l,r}=\max/\min_{k=l}^r(dp_{l,k-1}+dp_{k+1,r}+calc(l,r,k))\)

    • 其中,\(cal(l,r,k)\) 表示将 \(k\) 处决定的影响。

    • 我们来好好谈谈这个转移。不妨以 MYJ 为例(其的笛卡尔树性显然更明显),随着我们不断把最小值放下,所有含 \(k\) 的区间都要和放下的最小值取 \(\min\)

    • 但事实上,因为我们放下的最小值越来越大(沈阳大街,贡献定向!),所以每个区间一定在第一次被影响时就决定了。换言之,对于 \([l_i,r_i]\nsubseteq [l,r]\) 的每个需求区间,其一定包含了以前某次划分的 \(k\),即更早的最小值——也是更小的最小值。

    • 于是我们看到,对于 \(l,r\) 这个阶段,它将所有 \([l_i,r_i]\subseteq [l,r]\)\(k\in [l_i,r_i]\) 的需求区间解决了。

  • 实质上还是在划分区间,但这显然不是分裂式——它的分裂具有明显的 cdq 色彩,即以处理跨过划分界的需求或者说区间为核心。

  • 通常复杂度为 \(O(n^3)\),有时会有奇怪的预处理,各凭本事咯。

P3592 [POI2015] MYJ

  • 题意:求 \(\max_{i=1}^m(\min_{j=l_i}^{r_i} a_j\leqslant c_i?\min_{j=l_i}^{r_i} a_j:0)\),并输出对应的 \(a\) 数组。

  • 数据范围:\(n\leqslant 50,m\leqslant 4000\)

  • 还是要从数据范围入手。这个范围怎么看都很 \(O(n^2m\sim n^3m)\)

  • 显然可以对 \(c\) 离散化。

  • 强行上 DP!

    • 状态设计:\(dp_{l,r,k}\),表示 \(l\sim r\) 的最小值为 \(k\) 的情况下的最大总收益。这里的 \(k\) 就是它的特色辅助维,某种意义上是因为不带它没法转移而生的(需要保证笛卡尔树性,我指堆性)。

    • 初始化:...这个...看转移罢。

    • 状态转移方程:\(dp_{l,r,k}=\max_{x\geqslant k}(dp_{l,pos-1,x})+\max_{y\geqslant k}(dp_{pos+1,r,y})+calc()\),其中 \(calc\) 表示将所有 \([l_i,r_i]\subseteq [l,r] \And pos\in [l_i,r_i]\) 的结算为 \(k\)\(0\)(理由是不被完全包含的早就结算过了)。

  • 复杂度 \(O(n^3m)\)。显然这个 \(m\) 用常规手段优化不掉,dp 中可以考虑开一个后缀最大值的辅助数组,但 \(calc\)...唔,不太可行吧?

  • 关键是涉及到“完全包含”这个问题,于是若设 \(val_{l,r,j,k}\) 表示被 \(l,r\) 完全包含且过 \(j\) 的所有区间在 \(j\) 处收费为 \(k\) 时的贡献,光状态就 \(O(n^3m)\) 了,即使设法用线段树,枚举区间 \(i\),枚举包含它的 \(l,r\),交换 \(j,k\) 两维,然后对 \(k=c_i\) 处 rev 上去,乍一看是 \(O(mn^2\log)\),然而这里的价值不能直接前缀线段树查询(本质是对 \(k=c_i\sim 0\)\(k\)),最后还是要扫一遍整个数组推下来,更不要提为了 rev 我们交换后收费已经是不连续的了,上哪用前缀线段树啊...

  • 输出方案可以考虑对每个 \(dp\) 记录 \(\operatorname{argmax}\),并对 \(dp\) 的后缀最大值数组也记录 \(\operatorname{argmax}\)

P5851 [USACO19DEC] Greedy Pie Eaters P

  • 题意:

    • \(n\) 个物品,\(m\) 个需求,每个需求会将 \(l_i\sim r_i\) 中还剩下的物品全部取走。

    • 当且仅当某个需求确实取到了至少一个物品,你获得 \(w_i\) 的收益。

    • 规划顺序使总收益最大,输出这个最大总收益。

  • 数据范围:\(n\leqslant 300,m\leqslant n^2\)

  • 直接暴力上笛卡尔树式问题的一般 dp 设计。这里仅给出 \(calc\)

    • \(calc(l,r,k)=\max w_i([l_i,r_i]\subseteq [l,r] \And k\in [l_i,r_i])\)
  • 这里的笛卡尔树性(堆性的一方面)是隐式的:

    • 我们枚举的 \(k\) 事实上是最后一个被取的物品。

    • 对于一个固定的 \(k\),我们强制让最有价值需求在最后取走 \(k\),一定是不更劣的:因为当前取走 \(k\) 最多满足一个需求,换成其他需求一定不会更优,而所有能被 \(k\) 满足的需求在之后都没有被满足的可能,因为其一旦被满足就意味着 \(k\) 被取走,\(k\) 所满足的需求失配。

    • 换言之,我们枚举的点是区间内最后取的点,对应着过该点最大的需求。

  • 复杂度 \(O(n^3)\)

P4766 [CERC2014] Outer space invaders

  • 题意略,因为我一写题意肯定是简化版的,简化版的就把做法明写出来了。当然事实上放到这里就已经把做法明写出来了。

  • 直接粘思考过程,稍微修了一下格式:

  • 首先不妨离散化时间和距离。不失一般性地,规定只在某些 b 上使用激光。一个自然的思路是按 a 排序然后处理,但发现仍存活的外星人难以维护...毕竟显然没有朴素且正确的贪,这种做法怎么看都很容易退化成暴搜。

  • 转而考虑按 d 排序,更进一步地,按 d 降序。笛卡尔树!...唔,但这样的 dp 好像比较失一般性,我们不知道一个确定的 d 是在何时被干掉。

  • 但到这里也差不多猜到了,笛卡尔树式区间 DP 即可。首先把 a lower_bound 到 b 上。

    • 状态设计:\(dp_{l,r}\) 表示将可行时间在 \([l,r]\) 内(被 \([l,r]\) 完全包含)的所有外星人干掉的最小总代价。

    • 初始化:\(dp_{l>r}=0\)

    • 状态转移方程:\(dp_{l,r}=min(dp_{l,k-1}+dp_{k+1,r}+calc(l,r,k))\)。乍一看算一次 \(calc\) 的复杂度为 \(O(n)\)\(O(n^4)\) 炸了,但可以这个区间整体算。具体地,显然 \(calc(l,r,k)=\max_{[a_i,b_i]\subseteq [l,r]} d_i\),那么在 \(a_i\) 处打一个 \(d_i\) 的加入 tag,\(b_i\) 处打一个删除(之所以不打 \(r_i+1\) 是因为可能越界到别的区间,细节更多),开一个 multiset 从左向右扫过去处理最大值即可。

  • 复杂度 \(O(n^3\log)\),足够通过本题。这其实是一个很松的上界,具体地,我实现以为暴力对每个 \(i\) 扫过去就是对的...然后过了。事实上脑测一波会意识到这应该是能分析的,因为不被完全包含的区间没有最后一层的暴力扫。

  • 另外 \(calc(l,r,k)\) 显然是具备递推性的,对其也做一个 dp 应该能拆掉 \(\log\),把外星人挂到 \(a,b\) 上处理一下应该就可以,但需要改变转移顺序,至少记搜应该是不好用了。

  • 哦对了这里 \(calc\) 会有一点卡空间(全部开出来就 110M,满共 125M,不过其他部分好像不占多少空间,也就不到 10M,但我没敢开毕竟我是递归),我的解决办法是在递归函数里开临时的,长度为 \(len\)\(calc\) 数组。

  • upd:可以证明,只考虑 dp 区间内的所有最大权区间的并作为转移点,是对的。

    • 不妨将其他需求区间分为两类,即和转移点相交和不相交的。对于相交的,显然取最大权不更劣,因为选了最大权就不需要选它们,反过来则未必(除非两者都是最大权);对于不相交的,显然它们的操作顺序无所谓。

    • 对于单个转移点显然不够充分,但取遍所有最大权区间的并中所有的可能转移点显然是对的,事实上这代表着笛卡尔树序的选取一定不比任意树更差。我知道这一证明不太严谨,但我暂时没有想到更好的方法。

复杂区间 DP

P7914 [CSP-S 2021] 括号序列

  • 题意:

    • 给出一个由 \((,),* ,?\) 组成的字符串,其中 \(?\) 可以被指定为 \((,)\) 或 $* $。

    • 定义合法括号序列如下:

      1. ()(S) 均是符合规范的超级括号序列,其中 S 表示任意一个仅由不超过 \(\bm{k}\) 字符 * 组成的非空字符串(以下两条规则中的 S 均为此含义);

      2. 如果字符串 AB 均为符合规范的超级括号序列,那么字符串 ABASB 均为符合规范的超级括号序列,其中 AB 表示把字符串 A 和字符串 B 拼接在一起形成的字符串;

      3. 如果字符串 A 为符合规范的超级括号序列,那么字符串 (A)(SA)(AS) 均为符合规范的超级括号序列。

      4. 所有符合规范的超级括号序列均可通过上述 3 条规则得到。

    • 求合法方案数。

  • 数据范围:\(n\leqslant 500\)

  • 直接按题意模拟。也许会想到线性 DP 的做法,记录有多少个未匹配的左括号,但不对,因为 (SAS) 并不合法,此时无法知道本层是否放过 $* $。

  • 故考虑设计一个非常直白的状态 \(dp_{l,r}\)

  • 发现问题主要在于 2 中的分割(将 AB 中的某一项进一步分裂时,会有阶乘种分裂顺序也即算重阶乘次)。

  • 于是考虑一个很经典的做法:强制它从左向右断,断出来的左边不许进一步断,即加一个辅助维。

  • 后面的转移主要问题在于 ASB 这里,\(n^4\) 无法接受,用一个类似尺取法的前缀和优化就好了。

  • 主体大概是分裂式,不过也有一些从两头消的覆盖式的影子。

P4302 [SCOI2003] 字符串折叠 & UVA1630 串折叠 Folding & P2470 [SCOI2007] 压缩

  • 看起来挺相像的三道题,对吧?

  • 前两者大概是和涂色一样的另类分裂式。总体而言是一个比较经典的 dp 设计,\(dp_{l,r}=\min(\min_{k=1}^r (dp_{l,k}+dp_{k+1,r}),\min_{d\mid len \And s(l,r)=ks(l,l+d-1)} getlen(k)+2+d)\)。精髓就是找约数暴力,只有是约数的才涉及瓶颈部分的复杂度。

  • 压缩尤其有趣,大概要算覆盖式?至少我的做法,即那个 \(dp_{l,r,pl,pr}\) 的 4D1D 做法,是覆盖式的思路。强行邻项转移的典范,“掐头”思想用得很好。

  • 等等,我把自己骗了!那个做法的复杂度是 \(O(n^4)\),摆明了是个线性(\(r\) 总是不变)!

  • 哦,是的,我写丑了。有更好的状态设计:\(dp_{i,j}\) 表示处理完前 \(i\) 个,缓冲串从 \(j\) 开始。这是 \(O(n^3)\) 的,这是本质线性的,运用字符串哈希的话是 \(O(n^2)\) 的。妙啊!

P3736 [HAOI2016] 字符合并

  • 一道被严重低估的紫题,我认为。

  • 题意略。

  • 首先会很自然地想到挂载一个 \(2^K\)\(sta\),但显然是假的:操作顺序意义很大,首先可能操作以 \(sta\) 的左端点为右端点的 \(K\) 长区间,进一步地,为了操作这个区间可能要操作更左边但和它相交的区间以便于调整最后这个区间是以什么 \(sta\) 被操作的...

  • 故暴力挂载式根本行不通,该问题的合并是“到处都是”的,无法简单挂载在后面。

  • 考虑转而强行设计如下的 dp:

    • 状态设计:\(dp_{l,r,sta}\) 表示将 \(l\sim r\) 消成 \(sta\) 的最大总价值,这里 \(sta\) 是一个 \(0\sim 2^{K-1}-1\) 的二进制数。

      • \(sta\) 的哪几位有意义?

      • 从题目出发,可以发现长为 \(len\) 的一段总是会被消成 \((len-1)\bmod (K-1)+1\) 的最终段(毕竟 \(w\geqslant 0\)),故容易计算。

    • 初始化:\(dp_{l,l,a_l}=0,dp_{l,r,sta_{l\sim r}}=0\),特别地,\(dp_{l,r,sta_{l\sim r}}=v_{sta_{l\sim r}}(r-l+1=K)\)

    • 状态转移方程:分两种情况讨论。

      • \(sta\) 有不止一位:将最后的序列逆向展开,手推容易发现构成森林结构,换言之各个字符的来源互不相交。于是可以强行“掐头”,即将 \(sta\) 的最高位划出去。注意枚举左区间时应当只枚举能合成长度为 \(1\) 的最终区间的合法长度。

      • \(sta\) 只有一位:暴力枚举所有能消成 \(sta\)\(stan\),其为一个 \(K\) 位二进制数,然后仿照上面的做法。

  • 为什么能过?

  • 抱歉,我也不明白。这一算法的纸面复杂度上界是 \(O(n^22^{K-1}\times \dfrac{n}{K}\times (1 \text{ or } 2^{K}))\),理论上完全没有机会,而许多题解使用的还是非记搜实现。

  • 总之,这一 dp 设计中的思路对我们大有裨益:首先找到题目限制剪除无效转移(指 \(\dfrac{}{K}\) 这一部分)并利用其来设计状态(确定 \(sta\) 的哪几位有意义),然后利用森林性质进行邻项转移(在状压-区间混合 dp 中就是“掐头”,\(sta\) 抹一位,区间裂两半)。

posted @ 2023-01-10 10:23  未欣  阅读(176)  评论(0编辑  收藏  举报