线性 DP

概述

  • 线性 DP 并没有明确的定义。

  • 我个人认为,广义的线性 DP 指的是满足以下几个条件的 DP:

    • 存在至少一维可以线性推进;

      • 区间 DP 可以满足这个要求,只要在状态里放 \(len\)。在这一意义下,区间 DP 是“线性推进的一维为区间长度”的特化线性 DP。

      • 状压 DP 显然不满足,即使状态为 \(dp_{sta,i}\),占据主导地位的“层”实质上是 \(sta\)\(ppc\),当然你如果非说在这一不存在的“维”上线性那...我也只能同意...

      • 数位 DP 显然满足。在这一意义下,数位 DP 是“线性推进的一维为考虑到哪个数位”的特化线性 DP。

      • 树形 DP...过于特殊了吧?高度依赖树形结构。甚至也许我们应该认为线性 DP(链上的)是特化的树形 DP(树上的)...

      • 自动机上 DP。显然其并没有什么线性,如果一定要有,那么就是在自动机上移动的步数。

    • 转移较为简单,具有某种意义上的线性,通常可以使用网格图刻画;

      • 这个主要是把一些高度依赖记忆化搜索/拓扑排序的 DP 划出去。

      • 这种转移的简单往往是从状态设计的精巧而来的。具体来讲的话,DP 的本质在很大程度上就是抓取状态的关键信息...

      • 我们以背包 DP 为例吧。

        • 如果把选的集合视为点,物品视为边,直接建图,点数显然趋于指数级;

        • 而背包 DP 离谱的状态设计则把这样一个点数超多,边(也就是点的关系,也即转移)超复杂的图压成了一个简单网格图,然后偏移连边,某种意义上这里的层界就是物品。

    • 通常较为容易进行各种优化。

      • 至少我见到的大部分 DP 优化都是在线性 DP 上做的。
  • 我们会把线性 DP 作为 DP 的概述来使用。换言之,我们会在里面谈到很多 DP 的共性特点。

  • 实际实践中,线性 DP 的形式也是极不同的。故此处不给出更多的状态/初始化/转移上的共性,我们直接起各种类吧。

子序列式问题

  • 典型代表如 LCS 和 LIS。其实两者的 DP 形式共性不大...

  • 特点是对某种意义下的子序列求最长。好吧我们还是来看各个子类...

最长上升子序列(Longest Increasing Subsequence,LIS)问题

  • 经典模型为求一个串的最长上升子序列。不降/下降等问题显然与此问题本质同构。

  • 深入剖析的话,这实质上是二维偏序问题,即 \(i<j,a_i<a_j\)。也因此,它具有极重要的地位。

  • 通常复杂度(好像也是理论下界)是 \(O(n\log n)\)。下面给出几种能达到这个复杂度的 DP 设计:

  • 结尾导向法:

    • 状态设计:\(dp_{i,len}\) 表示考虑了前 \(i\) 个数字,长度为 \(len\) 的上升子序列结尾最小是多少。

      • 实际上第一维常常省略掉,因为 \(dp_i\)\(dp_{i+1}\) 几乎相同,不省略不仅空间炸而且时间炸。下面使用省略前的状态,但实际实现中请省略。
    • 初始化:\(dp_{x,0}=-\infty,top=0\)

      • \(top\) 是一个辅助转移用的指针,表示当前最长上升子序列长度。
    • 状态转移:一般我使用递推转移。记 \(k\)\(a_i\)\(dp_{i-1}\) 上的 \(lower\_bound\)(但不许相等),则有 \(dp_{i,j}=\begin{cases} dp_{i-1,j} & j\neq k+1 \\ \min(dp_{i-1,j},a_i) & j=k+1\end{cases}\)

      • 特别地,如果 \(k=top\),那么 \(top=top+1\)
    • 复杂度 \(O(n\log n)\),也可以使用线段树上二分或 set 来实现,但可能常数更大。

  • 长度导向法:

    • 状态设计:\(dp_{i,j}\) 表示考虑完前 \(i\) 个数字,结尾为 \(j\) 的 LIS 长度最大是多长。

      • 同样地,建议省略第一维。
    • 初始化:\(dp_{0,0}=0\)

    • 状态转移方程:\(dp_{i,j}\begin{cases} dp_{i-1,j} & j\neq a_i \\ \max(dp_{i-1,j},\max_{k<j}(dp_{i-1,k})+1) & j=a_i\end{cases}\)。非常恶心。

    • 这个的优点主要在于其具有一定的在线性。显然这个东西是用线段树来维护的,而它的本质是枚举 \(i\)(第一维),求 \(a_i\)(第二维)的前缀 \(\max\) 然后插入到 \(a_i\) 上,具有明显的在线风格。

    • 更进一步地,结尾导向法也可以用线段树来做在线,不过是把 \(a_i\) 当做第一维来枚举罢了。

    • 这两个 DP 状态同构。相关定义请看 LCS 那边。

    • 哦,复杂度 \(O(n\log n)\)

  • 钦定结尾法:

    • 状态设计:\(dp_i\) 表示以 \(a_i\) 结尾的最长上升子序列长度。

      • 这一状态设计是较为经典的极小状态集思想的体现。

      • 所谓“极小状态集”,指的是 DP 状态应当使用极小的状态来表示信息,如果某两维能够互相推出,就应当舍去一个。

      • 在这里的体现就是 \(dp_i\) 的状态虽然记录的是“用了 \(1\sim i\) 这一段”,但既然是以 \(a_i\) 结尾,那么我们也就知道了对应 LIS 的结尾,不需要为了转移额外记录 LIS 的结尾。

      • 这一思想在 \(\text{P2051 [AHOI2009] 中国象棋}\) 也有较为经典的体现:不需要记录多少列没有炮,因为若记 \(l\) 为没有炮的列数,则总有 \(j+k+l=m\),容易由 \(j,k\) 推出没有炮的列数。

    • 初始化:\(a_0=-\infty,dp_0=0\)

    • 状态转移方程:\(dp_i=\max_{j=1}^{i-1}(dp_j+1)(a_j<a_i)\)

    • 复杂度 \(O(n\log n)\),但是这个可以树状数组。而且输出方案也比较舒服一些(不过这需要把 ta 魔改一下)。

最长公共子序列(Longest Common Subsequence,LCS)问题

  • 经典模型为求两个字符串的最长公共子序列。

  • 各做法复杂度不一而同但都有应用场景,下面给出几种常用的 DP 设计:

  • 结尾导向法:

    • 状态设计:\(dp_{i,len}\) 表示 \(S_1\) 考虑了前 \(i\) 位,长为 \(j\) 的 LCS 在 \(S_2\) 的结尾最前是多少。

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

    • 状态转移方程:\(dp_{i,len}=\min(dp_{i-1,len},S_2.find(S_{1,i},dp_{i-1,len-1}))\)

      • 这里的 \(find\) 利用离散字符集后对每个字符记录出现位置,然后二分查找,做到 \(O(\log |S_2|)\)
    • 复杂度 \(O(|S_1|^2\log |S_2|)\)\(|S_1|,|S_2|\) 不同阶时表现较好。

  • 长度导向法:

    • 状态设计:\(dp_{i,j}\) 表示 \(S_1\) 考虑了前 \(i\) 位,\(S_2\) 考虑了前 \(j\) 位,LCS 最长多长。

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

    • 状态转移方程:

      • 泛用:\(dp_{i,j}=\max(dp_{i,j-1},dp_{i-1,j})\)

      • \(S_{1,i}=S_{2,j}\):还有 \(dp_{i,j}=\max(dp_{i-1,j-1}+1)\)

    • 这一 DP 的状态-转移网格图很有趣:

    • 实质上可以认为是斜边边权 \(1\),纵横边边权 \(0\),跑最长路。

    • 复杂度 \(O(|S_1||S_2|)\)\(|S_1|,|S_2|\) 同阶时表现较好。

  • 这两个 DP 有着同构状态。所谓同构状态,狭义来讲,指的就是各维(等号右边的也算一维)本质相同,只是位置不同(主要在于把哪一个放到等号右边)的 DP 状态。

  • 广义的同构状态是通过设计合适的初始化和转移能够等价地描述同一过程的多个状态。

  • 状态同构的 DP,转移和复杂度可能是不同的(LCS 就是一个很好的例子)。通常,选取哪个作为实际的 DP 状态,主要考虑以下几方面:

    • 状态数。把太大的放到等号右边。

      • 这在背包 DP 里特别典型:取 \(\sum w,\sum c\) 中较小的一个扔到等号左边。

      • \(\text{P6879 [JOI 2020 Final] スタンプラリー 3}\) 也是一个很好的例子,把答案放到维度里了。

    • 转移复杂度。选取转移复杂度较小的 DP 设计。

      • 譬如 LCS,因为实际题目中一般有 \(|S_1|,|S_2|\) 同阶,通常使用第二个 DP。
  • 转为 LIS 法:

    • 此做法想要优秀复杂度的话,高度依赖序列不可重这一性质。

    • 我们来分析一些性质。之前说了,LIS 的本质是二维偏序,那 LCS 呢?

    • 也是二维偏序,没想到吧!考虑不可重的简单情况,分别记 \(k_{1,i},k_{2,i}\)\(i\)\(S_1,S_2\) 中的出现位置,实质上是求一个序列使得 \(k_{1,i}<k_{1,j},k_{2,i}<k_{2,j}\)

    • 那自然是先把所有元素按 \(k_1\) 排序,也即以 \(S_1\) 为基础来做 DP。求出所有 \(k_2\),问题变成在 \(S_1\)\(k_2\) 数组上求 LIS,容易 \(O(n\log n)\) 解决。

    • 这个做法也可以推广到可重序列:

      • bacacabacc 为例,将 \(k_{2,i}\) 展开为一个单调递降的 vector,即,\((2),(3,1),(5,4),(3,1),(5,4)\)

      • 然后对该序列求 LIS。倒序理由显然,防止同一个字符被重复选取,有点类似多重背包。

      • 随机数据下表现较好,但显然可以被全同的序列卡到 \(O(|S_1||S_2|\log |S_1|)\) 的复杂度。

  • bitset 优化法:

最长公共上升子序列(Longest Common Increasing Subsequence,LCIS)问题

  • 经典模型为求两个串的最长公共上升子序列。

  • 基于两串长度是否同阶,主要有两种 DP:

  • 结尾导向法:

    • 状态设计:\(dp_{i,len}\) 表示以 \(S_{1,i}\) 结尾,长度为 \(len\) 的 LCIS 在 \(S_2\) 的结尾最前是多少。

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

    • 状态转移方程:\(\begin{aligned}dp_{i,len} & =\min_{j<i\And S_{1,j}<S_{1,i}}(S_2.find(S_{1,i},dp_{j,len-1}+1))\\ & =S_2.find(S_{1,i},\min_{j<i\And S_{1,j}<S_{1,i}}dp_{j,len-1}+1)\end{aligned}\)

      • \(\min\) 部分可以用树状数组来做,转移本身有 \(j<i\) 的固有顺序,插到 \(S_{1,i}\) 处就好。

      • \(find\) 部分可以对字符集离散然后记录出现位置上去二分查找。这部分可能有 \(O(|S_2|\log |S_2|)\) 的预处理复杂度,请注意。

    • 复杂度 \(O(|S_1|^2\log |S_2|)\)\(|S_1|,|S_2|\) 不同阶时表现较好。

  • 长度导向法:

    • 状态设计:\(dp_{i,j}\) 表示使用/浪费了 \(S_1\) 的前 \(i\) 个,以 \(S_{2,j}\) 结尾的 LCIS 长度最大是多少。

      • 我们看到这个设计的核心思路是 LCS 风格的状态,然后 LIS 风格的转移。
    • 初始化:\(dp_{0,0}=0\)

    • 状态转移方程:\(dp_{i,j}=\begin{cases} dp_{i-1,j} & S_{1,i}\neq S_{2,j}\\ \max_{k<j \And S_{2,k}<S_{1,i}}(dp_{i-1,k})+1 & S_{1,i}=S_{2,j} \end{cases}\)

      • 可以看出,正如某些 LIS 做法一样,这个 DP 的第一维可以压掉。

      • 之所以 \(a_i=b_j\) 时不考虑继承是因为用一个 \(l<i\) 和用 \(i\) 作为 \(a\) 中的结尾并没有什么区别,毕竟 \(a\) 这边是基础,是层。

    • 显然可以使用前缀最大值优化做 \(O(|S_1||S_2|)\)\(|S_1|,|S_2|\) 同阶时表现较好。

      • \(S_{1,i}\) 的限制对于同一层的转移是固定的,每次考虑新增的 \(j\) 作为 \(k\) 就好。
  • 实际上这两者都钦定了结尾。生活所迫啊!

    • 一定要钦定以 \(S_{1,i}\)\(S_{2,j}\) 结尾的话,转移会非常僵硬(难以邻项转移了),至少我只会 \(O(|S_1||S_2|\log |S_1|\log |S_2|)\) 的树套树二维前缀最大值做法,而且转移顺序非常恶心,是以值升序为第一关键字,以下标升序为第二关键字的顺序。

背包式问题

  • 典型代表如 01 背包,完全背包和多重背包这三个经典背包问题。

  • 较常见的推广的有分组背包,依赖背包,多维背包,泛化物品背包等。

  • 常见的问题主要有可达性,\(K\) 优解,\(K\) 优解方案数,字典序最小的 \(K\) 优解等。

  • 常用优化有二进制拆分,单调队列,分层 bfs ,基于四边形不等式的分治优化等。

  • 不可小觑啊!说回来,谈谈它们的共性。

  • 特点如下:

    • 给出一些物品,物品的本质是一个二元组(这里不考虑多维背包)\((c,w)\)

    • \(\sum c\leqslant lim\) 的条件下:

      • 能否恰有 \(\sum w=req\)

      • 求第 \(k\) 大的 \(\sum w\)

      • 其他问题。

      • 及满足对应要求的决策方案,可能还及字典序最小的方案。

    • 视题目不同,物品有不同的选取限制,可能是数量上的/顺序上的/...,泛化物品背包甚至将物品重构。

  • 状态设计通常为 \(dp_{i,j}\) 表示考虑完前 \(i\) 个/种/组物品,共花 \(j\) 的代价的最大总价值。有时我们也会采用其同构状态。

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

  • 状态转移方程...最朴素的是 \(dp_{i,j}=\max(dp_{i-1,j},dp_{i-1,j-c_i}+w_i)\),但实际上视情况而定,不一而同。

  • 经典三问的复杂度都可以做 \(O(n\min(\sum c,\sum w))\),其他的具体问题具体分析。

  • 注意,这是伪多项式时间。另外分层 bfs 在 \(\sum c\)\(\sum w\) 都过大时也许能够做比经典复杂度优秀得多的复杂度。下面默认 \(\sum c\leqslant \sum w\)

  • 把两个背包合并到一起的复杂度是铁铁的 \(O((\sum c)^2)\);在大部分背包问题中,这是一个很绝望的复杂度。如果有机会就多考虑暴力转,合并这个思路往往是没有前途的,因为暴力转可以设法重复利用转过的部分,而合并断无机会。

01 背包

  • 经典模型如下:

    • \(n\) 个物品,每个物品有代价 \(c\) 和价值 \(w\)

    • 每个物品都只能选一遍。

    • 求选取的物品的 \(\sum c\leqslant lim\) 的前提下的最大 \(\sum w\)

  • DP 设计如下:

    • 状态设计:\(dp_{i,j}\) 表示考虑完前 \(i\) 个物品,共花 \(j\) 的代价,所得的最大总价值。有时我们也会采用其同构状态。

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

    • 状态转移方程:\(dp_{i,j}=\max(dp_{i-1,j},dp_{i-1,j-c_i}+w_i)\)。后项需要 \(j\geqslant c_i\)

    • 通常会压掉 \(i\) 这一维,然后倒序转移以防止把同一个物品选了多遍。

  • 复杂度 \(O(n\sum c)\)

完全背包

  • 经典模型如下:

    • \(n\) 种物品,每种物品有代价 \(c\) 和价值 \(w\)

    • 每种物品都有无限个,即可以选无数遍。

    • 求选取的物品的 \(\sum c\leqslant lim\) 的前提下的最大 \(\sum w\)

  • DP 设计如下:

    • 状态设计:\(dp_{i,j}\) 表示考虑完前 \(i\) 个物品,共花 \(j\) 的代价,所得的最大总价值。有时我们也会采用其同构状态。

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

    • 状态转移方程:

      • 通常我们使用一种基于压维的转移。下面的内容认为把 \(i\) 这一维压掉了。

      • \(dp_j=\max(dp_j,dp_j-c_i+w_i)\)。后项需要 \(j\geqslant c_i\)

      • 该转移顺序进行,某种程度上是在“利用后效性”来实现把同一种物品选多次的效果。

  • 复杂度 \(O(n\sum c)\)

多重背包

  • 经典模型如下:

    • \(n\) 组物品,每组物品有代价 \(c\) 和价值 \(w\)

    • 每组物品有 \(num_i\) 个。

    • 求选取的物品的 \(\sum c\leqslant lim\) 的前提下的最大 \(\sum w\)

  • 多重背包的求解主要有三种方式。我们下面依次谈一谈:

  • 朴素 DP/01 背包拆分 DP(这里给出后者的实现,前者的实现缺乏启发性,且本质同构):

    • 状态设计:\(dp_{i,j}\) 表示考虑完前 \(i\) 个物品(把同种物品拆成 \(num_i\) 个),共花 \(j\) 的代价,所得的最大总价值。有时我们也会采用其同构状态。

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

    • 状态转移方程:\(dp_{i,j}=\max(dp_{i-1,j},dp_{i-1,j-c_i}+w_i)\)。后项需要 \(j\geqslant c_i\)

    • 复杂度 \(O(n\sum c\sum num)\)

  • 这一拆分太过粗糙。仔细考虑,我们希望的是“选取 \(0\sim num_i\) 个”的决策都能被选取,那么能否使用更高效的拆分方式,使得更少的 01 背包物品代表着那些决策?

  • 正是任何一种合理进制!但 \(k\) 进制的任何一位都要有 \(k-1\) 件 01 背包物品,故显然二进制拆分最优。

  • 二进制拆分优化 DP:

    • DP 本身同上,只是拆分物品时做二进制拆分。

    • 复杂度 \(O(n\sum c\sum \log_2 num)=O(n\sum c\log_2 \prod num)\)

  • 单调队列优化 DP:过于复杂,参看 单调队列优化 中的“单调队列优化多重背包”。

  • 广义多重背包(同组物品只有 \(c\)\(v\) 相同):参见 DP 优化-分治优化一节。

  • 三大经典模型看完了。那么,准备好迎接更多奇怪的限制、奇怪的物品和奇怪的优化了吗?

分组背包

  • 分组背包的主要特点如下:

    • 存在 \(K\) 个限制,每个限制是一个集合 \(S\)

    • 限制的形式是 \(S\) 内的物品只能选 \([L,R]\) 个/种/组(选 \(x\) 个种/组指的是在其中至少选了一个)。

  • 主要的区分点在于这几个:

    • 集合是否可交,乃至于物品间的矛盾关系是否可以进一步泛化。

    • \(L\) 是否 \(=0\)\(R\) 是否等于 \(|S|\)

    • 物品是哪种背包意义下的物品。

  • 这里我们先就最简单的情况进行讨论,即集合不可交,\(L=0,R=1\)

  • 为了方便,这里认为 \(S_1\cup S_2\cup \dots \cup S_K=n\),不行就手动补一些 \(|S|=1\) 的集合。

  • dp 设计如下:

    • 状态设计:\(dp_{i,j}\) 表示考虑完前 \(i\) 个集合的物品,共花 \(j\) 的代价,所得的最大总价值。可能改用同构状态。

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

    • 状态转移方程:\(dp_{i,j}=\max(dp_{i-1,j},\max_{k\in S_i}(dp_{i-1,j-c_k}+w_k))\)。后项需要 \(j\geqslant c_k\)

  • 复杂度 \(O(n\sum c)\)

  • 更泛化的矛盾:

    • 集合可交:似乎只能状压。

    • 更严苛的限制,譬如 \(|S_i\cup S_j|\leqslant 1\),或者不是集合而是区间,也许有更优的做法。

    • 进一步泛化矛盾的相关内容可以参看 状压 DP 中“依赖式问题”一节。

    • 特别地,考虑参看其中的 \(\text{P2051 [AHOI2009] 中国象棋}\),其本质上是一个很像线性的状压,有极其简洁(几乎可以称为线性)的状态,且在对这道题目的分析中,我们讨论了 dp 的本质。

  • \([L,R]\) 个:

    • 考虑暴力。

      • 每个集合 \(O(\sum\limits_{k=L}^R C(|S|,k)\times k\times \sum c)\)

      • 总复杂度 \(O(\sum\limits_{i=1}^K \sum\limits_{k=L_i}^{R_i} C(|S_i|,k)\times k\times \sum c)\)

    • 考虑朴素。

      • 设计一个集合内的 dp。

        • 状态设计:\(dp_{i,j,sumc}\) 表示考虑了前 \(i\) 个,选了 \(j\) 个,总代价为 \(sumc\) 的最大总价值。

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

        • 状态转移方程:\(dp_{i,j,sumc}=\max(dp_{i-1,j,sumc},dp_{i-1,j-1,sumc-c_i}+w_i)\)。啊显然这个 \(i\) 是可以压的。

      • 集合内 \(O(|S|R\sum c)\),集合间合并 \(O((\sum c)^2)\)

      • 总复杂度 \(O(\sum\limits_{i-1}^K|S_i|R_i\sum c+(K-1)(\sum c)^2)\)

    • 不能 wqs 二分!

      • 乍一看似乎在 \(sumc\) 固定时 \(sumv\) 关于 \(k\) 单峰,然而并不是这样。

      • 考虑三种物品分别有 \(1,2,3\) 个,属性为 \((v_1,sumc),(v_2,\dfrac{sumc}{2}),(v_3,\dfrac{sumc}{3})\)

      • 容易证明在 \(k=1,2,3\) 时决策方案唯一,于是只要适当构造 \(v\) 就 OK 了,很容易让它不凸。

        • 譬如 \(v_1=10,v_2=4,v_3=3\),甚至不单调。

        • 主要思路就是 \(v_i>v_{i+1}\) 然后乱搞。

P4141 消失之物

  • 题意略。

  • 可以看出,计数背包的转移本质上是若干线性变换,显然这是可以通过倒做背包的方式尾端撤销的。

  • 进一步地,可以看出这些变换本质上是可交换的(换言之,将背包数组看做元素,则加一个物品相当于一种运算,这种运算有逆元,可交换,且封闭)。故直接撤,总复杂度 \(O(nV)\)

P5391 [Cnoi2019] 青染之心

  • 题意略。

  • 显然直接做就是 \(O(qV)\) 的,问题在于空间会炸。

  • 建出版本树,考虑这样的 trick:每个重链开一个背包。显然,任何时候我们只需要 \(\log\) 个背包数组,空间是 \(O(V\log)\) 的,可以接受。

  • 到达重链头后,先将重链头更新进去,然后向各个轻儿子方向 dfs,全部处理完后再一路沿着重链走下去,此时该重链头的背包已经再也不会用到了,故直接在上面本地更新即可,不需要为了可撤销而存下来(撤销的话直接撤到上一条轻边以上了)。

  • 本题还可以增强到强制在线,有一个时间不变,空间 \(O(V\sqrt{q})\) 的做法,见囧仙的洛谷日报(我没看)。

杂谈

P5888 传球游戏

  • 题意略。

  • 主要的有趣点在于把同质的人合起来,以及全局打 tag,翻转转移的 \(O(km)\) 妙妙思路。

P3147 [USACO16OPEN] 262144 P

  • 题意略。倍增思想的完美运用,巧妙地将区间问题转化成线性问题。

  • 先谈怎么区间 dp:\(dp_{l,r}\) 表示合并的结果,如果不能合并成一个那么 \(=0\),转移暴力枚举断点,需要左右相同才能转移。答案在过程中统计。有点难度,但不太难。

  • 怎么线性?容易想到一个经典的倍增模型 \(dp_{i,j}\) 表示从 \(i\) 出发走 \(j\) 步,合出来最大是多大;然而这是不可转移的,两个相同的最大值之间可能还堵着一些东西。

  • 改状态!既然需要我相同,我就把相同放到状态里。这一合并过程也具有倍增的顺序,所以 \(dp_{i,j}\) 表示从 \(i\) 出发,把整个区间合成 \(j\),右端点在哪里,不可行则为 \(0\)。于是这一 \(dp\) 是可以转移的,但答案还是要在过程中统计,看最大的有效 \(i\) 是多少。

  • 总体而言,怎么说呢,非常新的一种东西,我对其的理解还需要加深。\(O(n\log)\)

P2470 [SCOI2007] 压缩

  • 参看区间 DP-复杂区间 DP。我把自己骗了,这是一道很妙的线性啊...

P1758 [NOI2009] 管道取珠

  • 题意:

    • 给定两个 01 栈。每次可以从还有数字的任意栈顶取一个并加到序列尾。

    • \(\sum\limits_{res} case_{res}^2\),其中 \(res\) 是生成的序列,\(case\) 是生成方案数。

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

  • 首先来一个惊为天人的转化:题意所求等于两个游戏同时进行且结果相同的方案数。

  • 理由就是双方各有 \(case\) 种方式达到。

  • 然后考虑设计一个最简状态 DP:

    • 状态设计:\(dp_{i,j,k}\) 表示取了 \(i\) 次,第一个游戏的第一个栈取了 \(j\) 次,第二个游戏的第一个栈取了 \(k\) 次,两个游戏生成的序列相同的方案数。

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

    • 状态转移方程:不想写...顺推来讲的话,就是如果栈里还有且相等,就转移过去,系数为 \(1\)

  • 妙啊!\(O(n^3)\)

P8321 『JROI-4』沈阳大街 2

  • 题意:给定排列 \(A,B\),求 \(\frac{1}{n!}\sum\limits_P f(P)\),这里 \(f(P)=\prod\limits_{i=1}^n \min(A_i,B_{P_i})\)

  • 超级妙妙题...做如下题意转化:

  • 注意到这里 \(P\) 可以认为是一个 \(B\to A\) 的双射,显然顺序是没有关系的,且乘多少取决于较小值,故将 \(A,B\) 合并为一个单调不增的序列。

  • 既然 \(P\) 是一个双射,那么这其实是一个匹配问题。我们依次考虑这个序列的每个元素,其有两种选择:

    • 做一个 \((A_x,B_y)\) 对中较大的一个,并留一个“插头”。

    • 做较小的一个,需要有一个对应的“插头”。

  • 据此设计 dp 如下:

    • 状态设计:\(dp_{i,j}\) 表示考虑了该序列的前 \(i\) 位,构成了 \(j\) 个对的所有方案的权值总和。

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

    • 状态转移方程:以 \(i+1\in A\) 为例,为

    \[\begin{aligned} & dp_{i,j}\to dp_{i+1,j} & k=1 \\ & dp_{i,j}\to dp_{i+1,j+1} & k=v_{i+1}[cnt_{B,i}>j] \end{aligned} \]

    • 其中 \(k\) 为转移系数。
  • 得解,复杂度 \(O(n^2)\)

  • 就其本质而言,这还是拆 \(\min\) 然后分别算贡献的思想;其特殊处在于其需要一个垫材(较大值)和元素只能用一次,为了创造 dp 机会,混成序列。

  • 规定在较大值处计算权值自然也是可以的,但可能导致挂的插头太多回收不了,这就是同构状态的东西了。

P3188 [HNOI2007] 梦幻岛宝珠

  • 题意略。

  • 注意到关键点在于 \(a\leqslant 10\);毕竟事实上每个数字都可以写成 \(a\times 2^b\)。故应当有某种根据二进制下数位的优化方式...

  • 由此出发,考虑按 \(b\) 分组,在每组内做如下的 dp:

    • 状态设计:\(f_{i,j}\) 表示考虑第 \(i\) 组,当前的总代价为 \(j\times 2^b\) 的最大总价值。

    • 初始化和转移略。转移用的维被我省了,干脆辅助转移吧反正这肯定不是复杂度瓶颈,至多 \(\sum\limits_{i=0}^{\log_2 n} i\times 10i\to O(n\times 10n)\) 罢了。

  • 接下来...问题变成怎么把 \(f\) 合并。朴素的暴力合并肯定是不行的,还不如分层 bfs;所以我们要玩一点小 trick,设法复现上面的 \(f\) 之所以对的原因——以 \(2^k\) 为整体系数,将代价这一维大大减小。我们直接看正解做法吧,毕竟太妙了我也想不出来:再起一个 dp。

    • 状态设计:\(g_{i,j}\) 表示考虑 \(b=0\sim i\) 的所有物品,当前的总代价不超过 \(j\times 2^b+\text{m 的低 i 位}\) 的最大总价值。

    • 初始化:\(g_i=f_i\)

    • 状态转移方程:\(g_{i,j}=\max_{k=0}^{500}(g_{i,j-k}+g_{i-1,k+\text{m 的第 i-1 位为 1}})\)。之所以只枚举到 \(500\) 是因为最多有 \(100\times 10=1000\) 的权在 \(i-1\) 上,事实上应当精细实现,用后缀和计算枚举上界。若超出 \(g_{i-1}\) 的上界,则值应当和上界相同,理由显然。

  • 显然复杂度上界为 \(30\times 500^2\),事实上远远达不到,故得解。

  • 这一 dp 到底在做什么?

    • 我们刚才说了要以 \(2^k\) 为整体系数。考虑一个固定的 \(k\),发现 \(m\)\(2^k\) 以下的那些位,凑满了也买不起哪怕一个 \(a\times 2^k\) 代价的物品;则不妨就让它们去买 \(k'<k\) 的物品。

    • 于是显然这个问题具备了递归性,递归到 \(k=0\) 结束;当我们已经有一个确定的 \(g_{k-1}\) 并准备转移出 \(g_k\) 之时,\(m\) 对我们来说不是一个数,而是三部分:\(a\times 2^k\)\(b\times 2^{k-1}\),更低位部分。前者代表着我们在这位上买东西和向 \(k-1\) 位推一些容量的总限制,第二者是“新被打进”买不起 \(a\times 2^k\) 代价的物品的一个值,第三者是被包含在 \(g_{k-1}\) 中的部分。

    • 从而在任意时刻,我们处理的都是一个只有一位的截面,从而我们将一个暴力的背包合并的代价维极大地减小了。

  • 更自然的设计:从高向低走,\(g_{i,j}\) 表示考虑完 \(b\geqslant i\) 的物品,还剩 \(j\times 2^i\) 的容量,已有的最大总价值。转移时 \(g_{i,j}=g_{i+1,k}+f_{i,2k-j+(m>>i\And 1)}\)。第二维显然是非常有限的一个维度,譬如 \(500\),因为更多的剩余容量也用不完。这其中的思想...非常有趣,我还没有参透,事实上我认为关键就在剩余容量用不完上。

P5289 [十二省联考 2019] 皮配

  • 题意略。本题的部分分设置堪称典范...我们采用第一篇题解里面的题意描述。

  • 首先我们强制 \(k=0\),考虑设计 dp。容易想到一个暴力 dp \(f_{i,j,k,l}\) 表示考虑了前 \(i\) 个城市,当前黄圆,绿圆,黄皱各有 \(j,k,l\) 个豆子的方案数。

  • 简单分析一下发现这个 dp 的复杂度在 \(O(nM^3)\) 左右,只要对每个豆荚枚举颜色,然后里面再枚举圆皱。然而还是太大了,\(M=500\) 都过不了。

  • 注意到我们其实并不关心每种豆子到底有多少个。改状态,变成 \(f_{i,j,k}\)\(j,k\) 分别为黄和圆。\(O(nM^2)\)

  • 然而这还是不能让我们满意。后面的两个 \(k=0\) 我们都吃不到。仔细分析,发现黄和圆的合法方案数可以分别算出来,这一部分的复杂度是 \(O(nm)\);于是总方案数为 \(\sum\limits_{i=S-C_0}^{C_1} f_i \sum\limits_{j=S-D_0}^{D_1} g_j\)

  • 至于为什么能够和实际的方案一一对应...因为颜色对于圆皱毫无影响,于是对于一个 \(g_j\) 对应的所有豆子分配,\(f_i\) 中的豆子都可以在圆皱中随意摇摆来满足。换言之,可以认为是我们先决定了每个豆子的颜色,然后再决定每个豆子的圆皱,乘法原理。或者也可以从解方程的角度出发,四元一次方程组,四个方程,解出来的解就是每种豆子的数量,而只有可能的豆子数量构成的方程才会有对应的方案数。

  • 转过来考虑 \(k>0\) 的情况,发现上面那个暴力 dp 是可以在过程中处理 \(k\) 的。到这里我们一共有 \(70\) 分可以拿。

  • 显然有 \(k\) 就不能随意摇摆了,但 \(k\) 很有限,更进一步地,因为每个豆荚的豆子数量 \(\leqslant 10\),特殊的豆子很有限。如果我们能把两种做法结合...先算一下复杂度。\(O(k\times (k\times 10)^2+nM)\),没考虑最后的统计答案,好像可以?但豆荚对于颜色的限制有些烦人。让我们考虑一下...

  • 不特殊,但与特殊豆子同豆荚的豆子,会被限制颜色吗?好像...容斥?不行,至少子集容斥不行,而且不可能记录每个特殊的豆子的性状...那代表元好像也没希望。

  • 稍加分析,发现现在问题主要在于我们的暴力 dp,在颜色被确定后可能有特殊的圆皱要求。这一匹配不是很简单...先把 \(dp\) 中对豆荚枚举颜色这个恶心的操作拆掉,加一个辅助维,然后保持同豆荚内豆子的连续性。

  • 还是不知道从何下手。先退一步,把不特殊的豆荚的颜色相关的 \(f\) 处理出来,这显然是不变的;然后...这些豆荚中的豆子照旧可以在圆皱之间随意摇摆,所以求一个对它们的 \(g\);现在只要 \(f\times g\),就是普通豆荚的方案数(当然和特殊豆荚合并的时候得考虑对豆子数量上限的约束)。这一部分的总复杂度...\(O(nM)\)

  • 接下来考虑特殊的豆荚。显然我们不能把其中每个豆子都当成特殊的豆子,那样一来可能全是特殊的豆子。不如看看 dp 长啥样吧家人们,\(dp_{i,j,k}\) 表示考虑完前 \(i\) 个特殊的豆荚,黄色有 \(j\) 个,圆的有 \(k\) 个的方案数。

  • 看来我们还是走在每个豆荚里面开一个子 dp 的路子试试...设 \(tp_{i,0/1,j}\) 表示当前考虑第 \(i\) 个豆子,该豆荚的颜色为黄/绿,当前有 \(j\) 个圆粒的方案数。内部转移只要枚举圆皱即可,单次复杂度 \(O(siz_i,M)\),总复杂度为 \(O(nM)\)

  • 好,大头来了,来尝试这个怎么看都很找死的背包合并。写一下式子:\(dp_{i,j,k}\times tp_{siz_i,0,l}\to dp_{i+1,j+siz_i,k+l}\),颜色为绿的和这个一样。复杂度为...\(O(M^3)\),找死,一次就寄了,总复杂度 \(O(kM^3)\) 因为最多是 \(k\) 个特殊的豆荚。

  • 显然复杂度不平衡。\(f,g\) 不去谈,那个和这些没啥关系,考虑设法以提高 \(tp\) 的复杂度为代价降低 \(dp\) 转移的复杂度。啊?这也可行吗?...除非大改状态,从本质上减少合并,换言之想办法把合并变成扔进去暴力跑背包。

  • 定义 \(tp_{i,j,0/1,k}\) 表示考虑了豆荚中前 \(i\) 个豆子,已经考虑过的豆荚的豆子中有 \(j\) 个黄色,当前豆荚的颜色,已经考虑过的豆荚加当前豆荚中已经考虑过的豆子中共有 \(k\) 个圆粒,的方案数。显然 \(j\) 是个不变量,我们到某个豆荚的时候直接把 \(dp\) 作为初始化扔进来就行,转移还是枚举圆皱,单次复杂度 \(O(siz_iM^2)\),总复杂度 \(O(nM^2)\)......??????

  • 好像是有问题。这么一来,\(k\) 的限制完全没有意义,只要把不特殊的豆子移到特殊的豆荚里就行了...我们仔细想想上面的乘法原理:先决定所有豆子的颜色,再决定所有豆子的圆皱。

  • 注意到特殊的豆荚里不特殊的豆子的圆皱还是可以摇摆的。所以我们现在应该是这样:决定不特殊的豆荚的颜色,决定不特殊的豆子的圆皱,然后决定特殊的豆荚的颜色,然后决定特殊的豆子的圆皱,当然最后两步可以合到一起。

  • 改定义!把 \(g\) 加上特殊豆荚的不特殊豆子。于是现在我们的暴力 dp 应该是不考虑不特殊的豆子的圆皱的,所以暴力合并的复杂度为内部合计 \(O(k)\),外部还是没救;但是这个平衡复杂度过后的内部复杂度就变成 \(O(kM^2)\) 了,乍一看还是很寄但是注意到 \(k\)(这里指的是 \(dp\)\(tp\) 的最后一维)的取值范围不过 \(10k\) 而已,所以内部应该是 \(O(10k^2M)\),可以接受。合并的时候转移式应为 \(dp_{i,j,k}=tp_{j-sum_i,0,k}+tp_{j,1,k}\)\(O(1)\) 的。

  • 问题来到最后的计数。考虑枚举 \(dp\),然后发现 \(f,g\) 各是一个前缀,求和即可。总复杂度 \(O(nM+10k^2M)\)

P5662 [CSP-J2019] 纪念品

  • 题意略。有趣的一道背包,重点是看出一件事:把纪念品捂在手里相当于先卖出去再买回来。

  • 那么问题变成每个天数变化中都要设法赚到最多的钱,于是不妨记 \(f_i\) 为第 \(i\) 天时手上最多有多少钱,问题变成 \(f\) 的转移;实质上,其就是一个 \(n\) 个物品,第 \(j\) 个物品价值为 \(v_{i+1,j}-v_{i,j}\) 的完全背包,容量上限为 \(f_i\)

  • 遂得解,\(O(TNM_{max})\),足够了。

23.2.15 T4 小球进洞

  • 题意:给出一个数轴,其上有 \(n\) 个球和 \(m\) 个洞,保证坐标各不相同。可以让所有还没有进洞的球同速地向左/右移动,求本质不同的进洞方案数。两个进洞方案本质不同,当且仅当有至少一个球进的洞不同。

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

  • 注意到球的位置不重要,到其左/右第一个洞的距离重要。把这个东西提出来,记作 \((x_i,y_i)\)

  • 提取出如下性质:若 \(x_i<x_j,y_i>y_j\),则不可能 \(i\) 进右且 \(j\) 进左。若先 \(i\) 进右则 \(j\) 亦进右,反之亦然。

  • 唔。对称的性质也是存在的,但已经够了...差不多。这是一种典型的二元偏序关系,考虑按 \(x\) 升序排序,于是随着 \(x\) 的增大...有点抽象啊。

  • 考虑记录一个历史最大右移,即考虑完前一段 \(x\) 之后,我事实上的历史最大右移量,不妨记为 \(y_{\max}\),于是以后的 \((x'>x,y)\) 对中,\(y\leqslant y_{\max}\) 的点都是没有自主权的:它们一定进了右边。

  • 事实上,我们这个过程相当于左移 \(1\)——右移 \(1\) 时的 \(y_{\max}\)——左移 \(2\)...,如此反复。容易看出这和所有左右移方案在效果上构成一个双射。

  • 则问题变成类似 LIS 的东西,每次我们可以选择不增大 \(y_{\max}\),也可以把它增大到某一程度,线段树/树状数组优化之,总复杂度 \(O(n\log)\)

posted @ 2023-01-09 15:17  未欣  阅读(68)  评论(0编辑  收藏  举报