状压 DP

概述

  • 状压 DP 是以状态含有某种意义上的状态压缩为特点的一类 DP。

  • 所谓状态压缩,通常指的是将各个元素的状态从常规的 vector 等编码映射为一个 \(id\) 即抽象的状态。

    • 较为常见的方式是压缩为一个 \(n\)\(k\) 进制数,其中 \(n\) 为元素数,\(k\) 为每个元素的状态数(中的最大值,理由见下),\(0\sim k^n-1\) 分别对应一种状态。

    • 固然可以 map,但是多一个 \(\log\)

    • 当各元素状态数不一的时候通常暴力编码建图连边(即将转移实际地建成边),不会使用简单的进制状压,因为无效状态太多。

    • 也有时“状态”是高度抽象的,如 \(\text{P2051 [AHOI2009] 中国象棋}\),此时几乎可以认为其是线性 DP。

    • 事实上将状压的状态不断简化就归约到线性,随着线性的状态难以规约决策就升维到状压,两者都指向 DP 的本质。

    • 这里的升维不一定是增多维数,也可能是将已有维度变得更复杂,包含更多信息。

    • 不过,不论如何,下文中,如无特别声明,默认使用进制状压。这才是最经典的狭义状压 DP。

  • 状态设计通常包括一个压缩的状态 \(sta\),如果是 \(k\) 进制数,则也称为 \(k\) 进制状态。

  • 初始化通常为 \(dp_{start,\dots}=\dots\),这里的 \(start\) 是初始状态,通常为 \(empty\)\(0\)

  • 转移上以状态为基,一般根据压物品和压属性有着不同的转移:

    • 压物品:逆推,\(dp_{sta,\dots}=\dots\),具体细节看是什么类的问题。

    • 压属性:

      • 比较舒服的是顺推,\(dp_{i,sta}\to dp_{i+1,sta},dp_{i+1,to}\),这里的 \(to\) 是原状态加入 \(a_{i+1}\) 后的结果状态。

      • 鉴于一般是逐物品枚举,无非选或不选,谈不上“优化转移复杂度”一说,所以就先不考虑很麻烦的状态转移方程了,毕竟得算除去某个物品的状态,而状态可能只满足结合性,不满足差分性。

      • 事实上,即使将转移方程写出来,转移来源也很可能是离散的,谈不上优化。

  • 更具体的,我们还是在下面分几类来谈。特别需要指出的是,很难的一些复杂状压 DP 我也把它们随便地分了个类,但它们也可以单独分一类叫“复杂状压 DP”,故——我是说,不要受问题形式限制。不要抱死依赖和需求不放。

依赖式问题

  • 典型代表如 \(\text{P5005 中国象棋 - 摆上马}\)\(\text{P2051 [AHOI2009] 中国象棋}\),前者为经典版,后者为魔改版。

  • 特点是给出 \(n\) 个物品,物品有价值 \(w\),物品之间有一些依赖关系,求选取方案的最大总价值或方案数。

    • 所谓依赖,可能是物品之间互相依赖对方存在或不存在。

    • 依赖可能不是直接连边来实现的,而是藉由对空间的占据等中间元素作为中继来构成依赖。

  • 状态通常为 \(dp_{sta,\dots}\) 表示当前选取了 \(sta\) 对应的物品集/有着 \(sta\) 对应的要素集(即作为中继的中间元素的集合),最大总价值是多少。视情况可能有各种辅助维。

  • 初始化为 \(dp_{start,\dots}=v_{start}\)(可能有必选之类的作为起始状态)。

  • 状态转移:

    • 压物品: \(dp_{sta,\dots}=\max_{j\in ava}(dp_{j,\dots}+w_{j,sta})\),其中 \(ava\)\(sta\) 可接受的来源状态。

    • 压属性:和概述讲的基本一样,不复制了。

    • 这里有一个有趣的问题:显然状压 DP 可以不管 \(sta\) 的合法性暴力转,最后遍历所有 \(sta\) 检查合法性来计算答案。

    • 然而有的题目里有更好的性质:

      • 如果 \(sta\) 合法,那么 \(sub\subseteq sta\) 都合法(这里的包含关系在进制状压下就是简单包含,编码状压下可能是某种拓扑序关系的上下游)。

      • 如果 \(sta\) 不合法,那么 \(sup\supseteq sta\) 都不合法。这里 \(sup\) 是超集 superset 的简写。

    • 显然,我们可以基于此只从合法点出发来转移,可以期望减小一定常数。然而有没有基于这种性质的更优解?

    • 事实上,这让我想起拟阵贪心的定义。也许带悔就可以?

  • 复杂度 \(O(k^n)\),辅助维开销具体问题具体分析。

P5005 中国象棋 - 摆上马

  • 题意:在 \(n\times m\) 的棋盘上摆任意个中国象棋的马(考虑蹩马腿的影响),求总方案数。

  • 数据范围:原题 \(n\leqslant 100,m\leqslant 6\)。可做 \(n=m=10\)

  • 依赖关系是马之间依赖对方的不攻击(不存在)。

  • DP 设计如下:

    • 状态设计:\(dp_{i,sta1,sta2}\) 表示考虑完前 \(i\) 行,第 \(i,i-1\) 行的放置情况分别为 \(sta1,sta2\) 的总方案数。

      • 大部分这种逐行转移的都可以滚掉第一维,后面不再专门指出。
    • 初始化:\(dp_{0,0,0}=1\)

    • 状态转移:

      • 首先有一个很 naive 的暴力顺推,总复杂度 \(O(2^{3m}\times n)\),足够通过本题,就是写起来不是一点恶心(考虑 \(O(2^{3m})\) 预处理矛盾情况,为了实现这个预处理可以 \(O(6\times 4)\) 地先算每个孤立马和已有 \(sta\) 的矛盾情况,然后整)。

      • 然后可以 FMT 优化,但有一些问题,主要是以下两个:

        1.怎么预处理极大不矛盾集合,总不能 \(O(2^{3m})\) 吧?

        2.求的是子集和吗?当 \(sta1'\subseteq sta1,sta2'=sta2\) 时,是否会有 \(sta2\) 中某个本来被蹩马腿的现在不被蹩了,于是矛盾了?

      • 解决办法:暴力枚举 \(sta1\)

        • \(sta,sta1,sta2\) 内部会蹩(\(sta\) 代表转移到的目标 \(sta\)),\(sta,sta1\) 会互相蹩,\(sta1\) 会蹩 \(sta2\)但是!\(sta2\)\(sta1\) 是无意义的!

        • 于是对于给定的 \(sta1\)\(sta2\) 这边可以子集和。问题变成暴力枚举 \(sta1\),然后 FMT 卷 \(sta2\)

        • 对于每个 \((i,sta1)\) 对需要 FMT 一次,总复杂度 \(O(2^m\times m\times 2^m\times n=2^{2m}\times m\times n)\),前项是次数后项是 FMT 复杂度。可以做 \(n=m=10\)

        • FMT 相关可以参看复合式问题一节的 \(\text{P1896 [SCOI2005] 互不侵犯}\),有较详细的说明。

P2704 [NOI2001] 炮兵阵地

  • 占据范围为伸出长度为 \(2\) 的十字,有些地方是障碍不能摆。其余平凡,还是双 \(sta\) 式,略。

  • 依赖关系为依赖不攻击(不存在)。

  • 复杂度也同上,不过是增强版的数据范围。当然暴力转也能过,因为可以预处理建图,有效状态和转移并不多。

P2051 [AHOI2009] 中国象棋

  • 题意:摆炮。

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

  • 依赖关系同上。

  • 这其实是个诈骗题。谨记一点:除非有不得不状压的理由,否则不要状压。

  • 对于炮/车/...来说,放在了哪一列并不重要,我们只关心放了多少列

    • 作为对比,之所以放王/马的时候要压放在哪里,是因为每列并不同质,靠边的和靠中间的影响并不一样。可以考虑 \(m=5\) 时的 \(01000\)\(00100\) 的差异,其中 \(1\) 处为王。

    • 至于炮兵啊...因为它有障碍,可能根本不能放,所以列与列不同。

  • 于是设计 DP 如下:

    • 状态设计:\(dp_{i,j,k}\) 表示考虑完前 \(i\) 行,有 \(j\) 列放了一个炮,有 \(k\) 列放了两个炮的总方案数。

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

    • 状态转移方程:较为复杂,考虑分类讨论。

      • 本行没有放炮:\(dp_{i-1,j,k}\)

      • 放了一个:\(dp_{i-1,j+1,k-1}\times (j+1)+dp_{i-1,j-1,k}\times (m-j+1-k)\)

      • 放了两个:\(dp_{i-1,j-2,k}\times C(n-j+2-k,2)+dp_{i-1,j-1,k-1}\times ...\) 不想写了总之就是把选哪些列的系数 \(C\) 出来。

      • 这些求和就是 \(dp_{i,j,k}\) 了。

  • 时间复杂度 \(O(nm^2)\)

  • DP 的本质是:

    • 将决策过程中的(复杂的,实际的)状态抽象为一组(simple and stupid 的)DP 状态,将决策的过程抽象为这些状态之间的转移(实质上同构于图论中的“点-边”模型,点即元素是状态,边即元素的二元关系是转移)。

    • 将实际上极不同的状态根据其关键信息的同异归类、压缩到少得多的 DP 状态里。

    • 采用的 DP 状态可能是复杂的进制状压或编码状压状态,也可能是高度压缩、抽象、简化的线性状态。如本题的 DP 状态就高度线性。

    • 以此,将朴素决策也即搜索的指数级复杂度规约到可以接受的多项式时间内。

  • 故时刻谨记,不要看到什么信息就把什么信息记录下来(指塞进 DP 状态里),而是要去繁就简,执简驭繁。

  • 对每个包含在状态中的信息,我们都要提问:你的必要性是什么?舍弃你就无法决策/无法正确地刻画过程了吗?如果不必要,把你舍弃能否获得更精简的状态?

  • 通过这样的极度抽象,将复杂的问题简单化。

P3226 [HNOI2012] 集合选数

  • 题意:

    • 这道题我审错题了...我们后面在 \(\text{P2150 [NOI2015] 寿司晚宴}\) 那里谈谈我以为的题及其解法。

    • 好了,说回来。定义一个集合 \(S\) 合法,当且仅当 \(\forall i\in S,2i,3i\notin S\)

    • \([1,n]\cap N\) 的合法子集数量。

  • 数据范围:原题为 \(n\leqslant 10^5\),考虑到实际上暴力转移只跑了近 \(700ms\) 应该可以更大,但本题的复杂度分析过于恶心,所以不去算可做的上界了。

  • 本题实际上是一个妙妙构造题。\(\forall i,(i,2i),(i,3i)\) 构成一组双向不存在式依赖(矛盾),但显然直接压根本无法接受。

  • 注意到考虑能否加入 \(i\) 的时候关心的东西其实很少,只有 \(sta\) 中的两个信息,于是(鬼知道为什么于是)想到这么一种构造:

    • \(\forall i,2\nmid i\land 3\nmid i\),以 \(i\) 为左上角构造一个矩阵也即网格图如下:

    • \(w_{x,y}=3\times w_{x-1,y}=2\times w_{x,y-1}\)。即横行公比为 \(3\),纵列公比为 \(2\),之所以这么放是因为我们习惯逐行推进,故让列数较小。

    • 那么发现每个矩阵内部的元素互相有限制,限制为相邻元素不能同时选。基于网格图的优秀性质,考虑压当前行选了哪些。

  • 故设计 DP 如下:

    • 状态设计:\(dp_{i,sta}\) 表示考虑完矩阵第 \(i\) 行,当前行选取情况为 \(sta\) 的总选取方案数。

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

    • 状态转移方程:\(dp_{i,sta}=\sum\limits_{from\subseteq sta\oplus full} dp_{i-1,from}\)。老规矩 FMT。

  • 则显然答案就是 \(\prod\limits_{i=1}^k (\sum\limits_{j=0}^{full_i} dp_{n_i,j})\),其中 \(k\) 为矩阵数。

  • 复杂度 \(O(\text{能过})\)

    • 反正无论如何,最大矩阵的贡献应该不超过 \(O(2^{\log_3 n}\times\log_3 n \times \log_2 n)\approx 2^{10}\times 10\times 17=1.7\times 10^5\),前面是 FMT 后面是次数,显然足够小。别在意那么多啦。

    • 其实还是可以在意一下。记 \(2^{\log_3 n}=K\),则有 \(\log_3 n=\log_2 K\),换底公式乱搞后 \(\log_2 K=1.58\log_2 n\),显然复杂度不超过 \(O(n\log^2)\)

    • 不过事实上...这个东西是低于线性的,\(n=10^8\)\(\approx 6\times 10^7\)

    • 不过也显而易见的是,只考察最大矩阵一定不对。大约有 \(\dfrac{n}{\ln n}\) 倍常数?谁知道呢...总之不要在意啦,休息一下吧。

P2150 [NOI2015] 寿司晚宴

  • 题意:求满足 \(S_1,S_2\subseteq [2,n]\cap N \land \forall i\in S_1,j\in S_2,i\perp j\)\((S_1,S_2)\) 对数量。

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

  • 依赖关系是依赖不互质的数不存在。

  • 数论题,准确的说是整除与同余相关,那么无脑考虑记录质数。

  • 显然直接压是不行的,考虑压小质因子。

    • 任何一个 \(\leqslant n\) 的合数都有至少一个 \(\leqslant \sqrt{n}\) 的因数。

    • 故可以考虑只压 \(\leqslant \sqrt{n}\) 的小质数,根据素数个数定理,小质数数量 \(\sim \dfrac{\sqrt{n}}{\ln \sqrt{n}}\)

  • 所以我们可以...等等。这不对。考虑一下,万一把 \(23\)\(46\) 选到了两边,怎么办?

  • 让我们再设法从根号分治这只橙子上榨出些汁子来:

    • 任何一个 \(\leqslant n\) 的合数都至多只有一个 \(\geqslant \sqrt{n}\) 的质因数。

    • 这代表着什么?这代表着,在把 \(<\sqrt{n}\) 的质因数压完即去除影响后,问题退化成一个朴素分组背包,分的组就是最大质因数(最大质因数 \(<\sqrt{n}\) 的可以认为是第零组)!

  • 所以将小质数编码,设计 DP 如下:

    • 状态设计:\(f_{i,sta}\) 表示考虑了前 \(i\) 组,当前每个小质数是没有选/在 \(S_1\) 中/在 \(S_2\) 中的方案数。

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

    • 状态转移:顺推考虑每一组,将 \(f_{i-1}\) 作为初始值,然后把 \(g_{end}\) 取出来作为 \(f_i\),其中 \(g_{end}\) 表示考虑完组内所有数的组内 DP 结果。

  • 组内 DP:

    • 状态设计:\(g_{i,sta,0/1/2}\) 表示考虑完当前组的前 \(i\) 个,每个小质数的状态,当前组对应的大质数的状态。

    • 初始化:记当前组为第 \(k\) 组,则 \(g_{0,sta,0}=f_{k-1,sta}\)

    • 状态转移:

      • 分讨 \(i+1\) 不选/入 \(S_1\)/入 \(S_2\),检验合法性,计算出目标状态然后转移。

      • 具体实现可以对每个数预处理一个 \(sta\) 表示其含的小质数集合。

  • 复杂度为 \(O(\sim 3^\frac{\sqrt{n}}{\ln \sqrt{n}}\times n)\),可以接受的数据上界大约为 \(n=1354\),总之足够通过本题。

  • 说回那个我看错题意的集合选数,我以为是任意倍都矛盾,即集合内得互质。于是相当于一个 \(2\) 进制 \(sta\) 的寿司晚宴(寿司晚宴其实我写的是两个二进制状态,因为好写)。

P4363 [九省联考 2018] 一双木棋 chess

  • 题意略。

  • 需求是左上都放过...好吧,这其实只是这个对抗搜索的状态特点。

  • 手推容易发现,已有状态集,我是指下过棋的地方,构成一个从左上角延伸出来的阶梯形,即 \(\forall i<n,to_i\geqslant to_{i+1}\),这里 \(to\) 是本行的极右延伸。

  • 于是暴搜之,发现只有 \(184756\) 的状态数。直接暴力记搜(这里我采用了双射进制哈希然后 u_map 暴力创过去),转移复杂度 \(O(10)\),属于是放在这有点画风不符的题目了。

  • 本题的数据显然可以进一步加强,我们谈谈更优的做法:\(\alpha-\beta\) 剪枝搜索。直接非常符合直觉地正着搜就行,大概起步能通过纸面复杂度 \(10^9\) 的数据范围。

  • 虽然上面的解法很自动机,但我想指出的是它算是一个另类的轮廓线 dp,轮廓线是阶梯状的。所谓轮廓线 dp,就是状压当前的边界,或者说已有连通块的轮廓...更具体的东西放在多米诺骨牌那里。

P3160 [CQOI2012] 局部极小值

  • 题意:给出一个 \(n\times m\) 的矩阵,求将 \(1\sim n\times m\) 不重地填入其中使得有且仅有某些地方是局部极小值的方案数。

    • 称一个格子中的数是局部极小值,当且仅当其是以其为中心的九宫格中最小的数。
  • 数据范围:\(n\leqslant 4,m\leqslant 7\)

  • 依赖关系是大小上的偏序关系。

  • 首先我们根本没见过 \(2^{28}\) 的复杂度,尽管它确实 \(\approx 2.7\times 10^8\)

  • 设法设计 DP,发现整个过程过于复杂,考虑约束它使得它可刻画。两种思路:

    1. 逐行转移;

    2. 从小到大填数。

  • 显然后者更自然,也更 specific。基于此,我们做一点简单的题意转化,记局部极小值为 x,非局部极小值为 o。

    • x:所在九宫格内最早填上。

    • o:不是所在九宫格内最早填上,且晚于九宫格内任意 x。可以进一步抽象成一类 o 和二类 o:

      • 一类 o:周围有 x。比周围的 x 晚就好。

      • 二类 o:周围无 x。不是最先填就好。

  • 基于此,我们已经可以做 \(O(2^{nm}\times (nm)^2)\) 的朴素 DP 了。发现 \((nm)^2\) 来自:

    1. 枚举填到谁了;

    2. 枚举填到哪里去。

  • 以普遍理性而论,1 的复杂度肯定消不掉(bitset 等盘外招当然不在讨论之列)。这就告诉我们这个状态没前途,得想办法把这个 \(2^{28}\) 的 state 砍下来,别老惦记你那完全信息了。

  • 一个自然的想法是,考虑什么方式可以把 2 的复杂度消掉。

    • 观察到 x 最多只有 \(\lceil\dfrac{n}{2}\rceil\lceil\dfrac{m}{2}\rceil\to 8\) 种,显然这个可以考虑扔在那里。

    • 于是考虑到一件事:我们手上的 o,只要“解锁”了,就没有区别,都是转移系数。所谓“解锁”,就是可以填了。

  • 这就启示我们正解大概率只压了 x 的情况。我的思考就到这里,因为后面我误入歧途了,下面会是“做过之后重新看”的感觉。

  • 只压 x,只压 x 是多少?\(2^8\),就算算上枚举该填哪个数(现在 o 都成转移系数了当然不会有填到哪去的转移复杂度),也就 \(2^8\times 28\),而 \(\dfrac{10^8}{2^8\times 28}\approx 13950\)

  • 大坑还在后面哪!这么冗余的复杂度使用,其实就是暗示我们,除非使用升维等方式,否则绝对不可做,否则没有必要数据范围这么小。

  • 回过头来考虑我们的 DP。

    • 现在它大概长成 \(dp_{i,sta}\) 的样子,表示填了 \(1\sim i\)\(sta\) 中的 x 填过了。

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

    • 转移呢?怎么转移?

      • 填一个 x:非常简单。

      • 填一个 o:...

  • 发现我们拼了老命也不过是能预处理出一类 o 的可行性,我指的是知道目前有多少个一类 o 解锁了。而二类 o 的解锁情况,几乎不可避免地指向全信息状态——

  • 仔细想想,我们为什么老是关心二类 o 的解锁情况?如果没解锁就填了,会发生什么?

  • 那么这个 o 会成为一个实质 x。我们构造出了比 \(sta\) 更多的 x。

  • 好,那么我们考虑一下:就这么 DP 的话,我们的 DP 含义是什么?

  • 我计划让 \(full\) 内为 x,当前填了 i 个数,当前 \(full\) 的子集 \(sta\) 已经是 x 了,但实际上我不知道是不是在 \(full\) 外还有 x,的总方案数。最后的 \(dp_{n\times m,full}\) 就是我的结果。

  • 那翻译一下岂不就是 x 构成一个 \(full\) 的超集的方案数吗!容斥!暴力构造 full 的所有超集,然后容斥。可以证明,超集的元素规模(指二进制状态大小)也是 \(2^8\) 的,但超集大小呢?

  • 暴搜发现共有 \(16334\) 种合法状态(只考虑了 x 不冲突,未考虑 x 的下界)。伏笔回收。

  • 总复杂度大约 \(2^8\times nm\times 16334\approx 1.17\times 10^8\),考虑到这是个 \(O\),肯定足够通过本题。

P5369 [PKUSC2018] 最大前缀和

  • 题意:求一个序列的所有排列的最大前缀和之和。特别地,最大前缀和对应的前缀不能为空。

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

  • 依赖大概是...嘿嘿。下面说。

  • 首先我们非常自然地想到去算每个 \(sta\) 中的数字构成最大前缀和来源的方案数。不妨约定,把一个排列的贡献归给极长的那个和为最大前缀和的前缀。

  • 容易想到直接暴力计算方案数为 \(ppc(sta)!\times (n-ppc(sta))!\) 然后容斥,需要向前容斥和向后容斥:

    • 不能有一个更短的前缀,其和大于 \(sum_{sta}\)

    • 不能有一个更长的前缀,其和大于等于 \(sum_{sta}\)

  • 显然这玩意双向没法做,于是想到对 \(sta\)\(sum\) 降序排序,相当于 toposort 了。发现能做,但是 \(3^n\),除非维护带修子集和和超集和。1.12upd:这种魔幻的东西好像是存在的,半在线 FMT,要求半在线过程是符合高维前缀和的拓扑序的。不过这里的转移...好像只能满足两者之一的拓扑序吧,不能同时满足子集和的和超集和的。

  • 啊哈哈...那我们来审视一下它为什么要容斥吧。

    • 向前:可以转化成 \(1\sim ppc(sta)\) 这一段不能有一段真后缀的和 \(<0\)

    • 向后:可以转化成 \(ppc(sta)+1\sim n\) 这一段不能有一段前缀的和 \(\geqslant 0\)(记得吗?我们把贡献归给最长的了)。

  • 发现每个 \(sta\) 依赖的是内部排列的合法性和补集排列的合法性,且两者相互独立。考虑怎么做这个玩意的 \(O(2^n\times n)\sim O(2^n\times n^2)\)

  • 不妨设计 DP 如下:

    • 状态设计:\(f_{sta},g_{sta}\) 分别表示 \(sta\) 的没有后缀和 \(<0\)/没有前缀和 \(\geqslant 0\) 的排列数。

    • 初始化:...可以声明说 \(f_0=g_0=1\)?这其实是不合定义的,一个更 specific 的初始化应该是 \(f_{2^x}=a_x\geqslant 0,g_{2^x}=a_x<0\)

    • 转移:好像方式比较多,就说一下我的写法吧,是一个我在题解里看到的过分妙的转移。

      • 强制把 \(f_{sta}\) 看做是由所有 \(f_{sta'}\) 转移来的,其中 \(sta'\subsetneq sta\)\(ppc(sta)-ppc(sta')=1\),这种“转移”指的是尝试给 \(sta'\) 对应的排列前面加一个数字。

      • 于是转移式是这样的:\(f_{sta}=\sum f_{sta'}\times [sum_{sta}\geqslant 0]\)\(sta'\) 定义见上。既然 \(sta'\) 的那些排列本身合法,那么在开头加一个数只新构造了一个长度为全长的前缀需要检验!

      • \(g\) 的转移是对称的。关于前缀与真前缀的问题,请自行微调解决。

  • 复杂度 \(O(2^n\times n)\),善哉。

某 T4 序列奇偶(parity)

  • 题意:给出不定序列 \(a_{1\sim n}\),其中 \(a_i\in [dw_i,up_i]\),另有 \(m\) 条形如 \((l,r,w)\) 的限制,表示第 \(l\sim r\) 项的和模二余 \(w\),求合法方案数及字典序最小的方案。

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

  • 看起来就很折半...容易想到压前缀和数组,由此有一个简单的暴力,\(O(2^n\times n)\) 左右的。

  • 考虑折半。发现需求形如“某些地方为 \(0\),某些地方为 \(1\),剩下的无所谓”,乍一看是不可做的但注意到被限制的地方都一样,于是只要在无限制的维度上做高维前缀和即可,构造方案的话...先贪左边最小的(这是容易的,只要其本身有值且配对有值即可,但需要操作一下记 \(vis\) 并从 \(vis\) 的地方出发,因为模之后为 \(0\) 不代表没有值),然后暴力 \(O(2^\frac{n}{2}\times \frac{n}{2})\) 右边的配对的最小字典序。比较恶心。

  • 一种美妙的思路是将前缀和之间的限制视为边,于是大小为 \(1\) 的连通块可以不枚举,从而总枚举 \(O(2^\frac{n}{2})\),然后直接线性地 dp 一下处理方案数,字典序可以用类似分层 bfs 不断算 \(pri\) 的方式来处理,对每个枚举可以求出对应的最小字典序,不断记录最小值,可以做到 \(O(2^\frac{n}{2}\times n)\) 的时间和 \(O(2^\frac{n}{2})\) 的空间(本题卡空间,128M)。

需求式问题

  • 典型代表如 TSP 及泛化 TSP 和 \(\text{P5366 [SNOI2017] 遗失的答案}\) 等,两者分别是物品需求式和属性需求式的典例。

  • 特点是给出 \(n\) 个物品,求满足某种需求的最小总代价/总方案数。

    • 获取尚未获取的物品的可行性和代价可能是关于当前已经获取的物品集的函数。
  • 状态通常为 \(dp_{sta,\dots}\) 表示已经有了 \(sta\) 对应的物品集/性质集的最小总代价。

  • 初始化为 \(dp_{start,\dots}=c_{start}\)(可能起始状态就有某些物品/性质,并强制付过了对应代价)。

  • 状态转移大体分两类:

    • 压物品:\(dp_{sta,\dots}=\max_{j\in sta}(dp_{sta\oplus j,\dots}+w_j)\)

    • 压属性:

      • 比较舒服的是顺推,\(dp_{i,sta}\to dp_{i+1,sta},dp_{i+1,to}\),这里的 \(to\) 是原状态加入 \(a_{i+1}\) 后的结果状态。

      • 鉴于一般是逐物品枚举,无非选或不选,谈不上“优化转移复杂度”一说,所以就先不考虑很麻烦的状态转移方程了,毕竟得算除去某个物品的状态,而状态可能只满足结合性,不满足差分性。

      • 事实上,即使将转移方程写出来,转移来源也很可能是离散的,谈不上优化。

  • 复杂度 \(O(k^n\times n^2)\),其中 \(k\) 为状态的进制。辅助维开销具体问题具体分析。

P1171 售货员的难题

  • 题意:求将每个点都恰访问一次后回到起点的最小总路程。几乎就是经典 TSP。

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

  • 需求是都恰访问一次。

  • DP 设计如下:

    • 状态设计:\(dp_{sta,i}\) 表示访问过 \(sta\) 中点,停留在 \(i\) 的最小总代价。

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

    • 状态转移方程:\(dp_{sta,i}=\min_{j\in sta} (dp_{sta\oplus sta_i,j}+c_{j,i})\)

  • 复杂度 \(O(2^n\times n^2)\)。请记住 \(n=20\) 是这个复杂度的标志数据范围。

P4011 孤岛营救问题

  • 题意:在一张 \(n\times m\) 的网格图中求从 \(S\)\(T\) 的最短路。特别的是,有 \(K\) 把钥匙,某些边需求对应钥匙。某些边不存在。

  • 数据范围:反正就是 \(O(nmkey\times 2^K)\) 的范围呗,其中 \(key\) 为关键点(钥匙或 \(S,T\))个数。

  • 需求是走到 \(T\),钥匙是实现需求的手段。

  • 暴力 bfs 预处理在有 \(sta\) 的钥匙时,从 \(u\)\(v\) 的最短路长度。于是有如下 DP:

    • 状态设计:\(dp_{i,sta}\) 表示当前在 \(i\),有 \(sta\) 的钥匙,最小总路程。

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

    • 状态转移:直接顺推,事实上这个应该比较像拓扑排序风格。

  • 足够通过本题。

P3947 肝活动

  • 题意略。

  • 需求是得分和都打过。

  • 设计 DP 如下:

    • 状态设计:\(dp_{sta}\) 表示打完 \(sta\) 中的歌的最大得分。

      • 当前的时间是隐含在其中的。这是典型的极小状态集。
    • 初始化:\(dp_0=0\)

    • 状态转移方程:顺推吧,无非 \(O(2^n\times n)\)。这一复杂度的标志性数据范围是 \(n=22\)

  • 足够通过本题。

P2831 [NOIP2016 提高组] 愤怒的小鸟

  • 题意略。

  • 需求是都杀了。

  • 发现每次暴力选两个构造直线然后检验的复杂度是 \(O(2^n\times n^3)\),无法接受。

  • 考虑预处理曲线。预处理复杂度 \(O(n^3)\),将 dp 部分降到了 \(O(2^n\times n^2)\)

  • 可以进一步优化!强制钦定一定要打死编号最小的猪。于是复杂度为 \(O(2^n\times n)\)。强制顺序来将本质相同的方案的各种排列的复杂度舍去,这很妙。

P3959 [NOIP2017 提高组] 宝藏

  • 题意略。

  • 需求是连通。也可以说是“把所有点都装进连通块”。

  • 第一眼确实很 Prim,但想一下会发现这个东西明显有后效性。

  • 反正是 TSP 板子,直接 \(O(2^n\times m)\) 创过去吧!

  • 更精巧的解法?考虑使用邻接矩阵。会好一些,大概是 \(O(2^n\times n^2)\)。没有什么特别的一道题。

P2322 [HNOI2006] 最短母串问题

  • 题意略。从题目名称应该就能看出来。

  • 需求是遍历。

  • 谈一下数据范围:\(n\leqslant 12,|S|\leqslant 50\)。这给了我们一个使用比较暴力的方法的机会:

  • 强行建 AC 自动机,然后一个点拆成 \(2^n\) 个点,跑 bfs(事实上有许多点是没有意义的点)。最后把所有解构造出来,排序,取字典序最小的。

  • 一个小细节是 \(rsta_{now}=sta_{now}|sta_{fail_{now}}\)

  • 这种做法的主要问题是卡空间,毕竟时间只有 \(O(\sum \times 2^n)\approx 2.5\times 10^6\)。下面谈一种更优美也更优的做法。

  • 对于一般的 TSP 问题,我们一旦从上一个点出发,就必然是在去下一个点的路上。在 AC 自动机上显然不是,所以我们设法规约它:

  • \(O(n^2|S|)\) 暴力匹配最大有效后缀,然后在串与串之间连边。字典序问题还是最后排序来解决,但复杂度好了不少,为 \(O(n^2|S|+2^n\times n^2)\approx 6\times 10^5\)

P5366 [SNOI2017] 遗失的答案

  • 主体部分请参看 P5366 [SNOI2017] 遗失的答案 解题报告

  • 鉴于其是早期作品,内容缺乏公理化和规约,这里给出简述:

  • \(\gcd\)\(\operatorname{lcm}\) 的要求,隐含着质因数的要求。

  • 完成此步转化并都 \(/G\) 后,题目的真面目就浮现出来,即对 \(L\) 的质因数的次数的上/下界需求。

  • 因为 \(\omega(L)\leqslant 8\),所以可以直接压。至于后面的正反 DP、FMT 合并等,都是较为顺理成章的事情。

P3092 [USACO13NOV] No Change G

  • 题意略。有点另类的需求式问题。

  • 需求是全买的同时省钱。

  • 抓住必须按顺序买物品这一点,我们可以二分转移。

  • 总复杂度 \(O(2^n\times m\log m)\),这里我把硬币数记为 \(n\),物品数记为 \(m\)

P5776 [SNOI2013] Quare

  • 题意:给定一张 \(n\)\(m\) 边的图,边有边权,求边权和最小且 \(V'=V\) 的边双连通生成子图的权值,即使得全部 \(n\) 个点边双连通的最小权边集 \(E'\) 的权。

  • 数据范围:\(n\leqslant 12,m\leqslant 40\)。可做到约 \(n=16\)

  • 数据范围非常怪,dp 起来也完全无从下手。我们直接照搬 ix35 的题解(对原文做了一些修辞上的微小改动):

  • 首先我们需要了解耳分解

  • 这里这样定义耳(不知道是否标准):对于(强)连通图 \(G\),耳是一条路径 \(a_1\to \ldots\to a_m\),满足 \(a_1,\ldots,a_{m-1}\) 两两不同且 \(a_2,\ldots,a_m\) 两两不同(也就是说是一条简单路径或简单圈),并且 \(a_2,\ldots,a_{m-1}\) 的度数都为 \(2\),并且删除这上面所有边后不影响 \(a_2,\ldots,a_{m-1}\) 以外点的(强)连通性。

  • 定义一张图是可耳分解的,当且仅当可以每次从中删去一个耳,并且最终所有边都被删除。

  • 有如下定理:

    • 一张有向图是可耳分解的,当且仅当它强连通。

    • 一张无向图是可耳分解的,当且仅当它边双联通。

  • 于是我们有一个 naive 算法,倒做耳分解,往一个空图中不断加耳。

  • \(f(S)\) 表示包含集合 \(S\) 中所有点的最小权双连通分量,枚举与 \(S\) 不交的点集 \(T\),将 \(T\) 连成耳后两端与 \(S\) 中的某两个点连接即可,时间复杂度为 \(O(3^n\times \operatorname{poly}(n))\)

  • 但事实上我们可以做一点优化。上面将耳看做了一个整体,但我们不妨将耳逐步加入,具体地,令 \(f(S,i,j)\) 表示当前考虑了 \(S\) 中的点,但是 \(S\) 中最后加入的一个耳还是不完整的,耳伸出去部分的一个端点是 \(i\),最终需要与 \(j\) 连接上,此时已经加入的所有边的最小权值和。

  • 那么转移只要每次枚举耳上 \(i\) 的后继即可。时间复杂度为 \(O(2^n\times \operatorname{poly}(n))\),注意实现细节。

  • 注:这事实上和背包 dp 的暴力合并到精细手操的优化方式有异曲同工之妙!我的实际实现中采用了如下的 dp 设计,可以做到 \(O(2^nn^3)\)

    • 状态设计:\(f_{sta,i,j}\) 表示 \(sta\) 内的点已经被加入边双,当前未处理完的耳我们希望它和 \(i\) 接,耳的端点是 \(id\)\(j\) 的边来的(记录边的起终点)的最小总代价。特别地,如果 i=n,j=0,代表着目前没有耳。

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

    • 状态转移:

      • 当前有耳:枚举耳上端点的出边,尝试延展耳,\(O(n)\);尝试结算耳,\(O(1)\)

      • 当前无耳:枚举期望接到哪里,枚举耳的起点,枚举出边。约 \(O(n^3)\)

  • 乍一看时间是远高于我们说的 \(O(2^nn^3)\) 的,但实际上注意到无耳的转移对每个 \(sta\) 最多发生一次,故总复杂度恰为 \(O(2^nn^3)\),且跑不满。

CF gym 102759C Economic One-way Roads

  • 题意:上一道题的强连通版本,且图是完全图。

  • 还没做,做了会稍微简记一下。

9.12 T2 技能大赛

复合式问题

  • 典型代表如 \(\text{P1896 [SCOI2005] 互不侵犯}\)\(\text{P4460 [CQOI2018] 解锁屏幕}\),分别为物品需求和属性需求的典例。

  • 特点是给出 \(n\) 个物品,物品之间有一些矛盾关系,同时要求选一个物品集以满足某种物品/属性上的需求,求最小总代价或方案数,即矛盾式问题和需求式问题的复合。

  • DP 设计基本是基于矛盾式或需求式来做,视情况升维。这里不详谈,看题吧。

P1896 [SCOI2005] 互不侵犯

  • 题意:在 \(n\times n\) 的棋盘中放 \(K\) 个国象的国王,使他们不互相攻击,求方案数。

  • 数据范围:原题 \(n\leqslant 9\),可以做 \(n\leqslant 15\)

    • 这里认为 \(1s\) 在正常常数下可以跑 \(4\times 10^8\)
  • 其依赖是不攻击(不存在),需求是放 \(K\) 个。

  • 首先我们算一下 \(K\)。容易证明,\(K\leqslant \lceil\dfrac{n}{2}\rceil^2\)

  • 注意到王的攻击范围很狭窄,压 \(n\times n\) 显然没有必要。考虑逐行转移。

  • 考虑设计 DP 如下:

    • \(dp_{i,j,sta}\) 表示考虑了前 \(i\) 行,当前一共放了恰好 \(j\) 个国王,第 \(i\) 行的国王放置情况为 \(sta\) 的总方案数。

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

    • 状态转移:

      • 朴素转移:顺推比较好写。

        • 暴力枚举下一行的放置情况,判定合法性并转移。

        • 转移复杂度 \(O(2^n)\),总复杂度 \(O(2^{2n}\times n^2\times K)\)

        • \(n=9\)\(\approx 5.3\times 10^8\),考虑到这种写法的转移起点必须合法,应该有足够通过的小常数。

      • FMT 优化:

        • \(dp_{i,j,sta}=\sum\limits_{from\subseteq ava} dp_{i-1,j-ppc(sta),from}\)。其中 \(ava\) 是不与 \(sta\) 矛盾的极大状态(注意这里的 \(ava\) 是虚状态,其内部可以自相矛盾)。

        • 显然这可以用 FMT 优化求子集和,对于每个二元组 \((i,j)\)\(O(2^n\times n)\) 的复杂度,故总复杂度为 \(O(2^n\times n^2\times K)\)

        • \(n=15\)\(\approx 4.7\times 10^8\),足够通过本题。

P4460 [CQOI2018] 解锁屏幕

  • 题意:给出 \(n\) 个二维平面上的点,求合法的解锁屏幕手势有多少种。一个点的有序集合 \(S\) 被称为合法的解锁屏幕手势,当且仅当:

    • \(|S|\geqslant 4\)

    • \(\forall u,v\in S\land nxt_u=v\)\(u,v\) 的连线经过的点必须都访问过(除了 \(v\))。

  • 数据范围:\(n\leqslant 20,|x|,|y|\leqslant 10^3\)

  • 它的依赖不能经过尚未访问过的点,需求是 \(ppc\geqslant 4\)。其实有点像 TSP。

  • 故考虑暴力预处理 \(sta_{u,v}\) 表示 \(u,v\) 连线经过的点,然后做如下 DP:

    • 状态设计:\(dp_{sta,i}\) 表示经过了 \(sta\) 中的点,当前停留在 \(i\) 的总方案数。

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

    • 状态转移方程:\(dp_{sta,i}=\sum\limits_{j\in sta \land sta_{j,i}\in sta} dp_{sta\oplus sta_i,j}\)

  • 也许可以优化但我不想考虑了,\(O(2^n\times n^2)\),足够通过本题(虽然看起来是 \(4\times 10^8\) 但显然这个复杂度溢出不少,常数足够优秀)。

P2595 [ZJOI2009] 多米诺骨牌

  • 题意略。本题的容斥部分也即核心在容斥原理那边,我们在这里只谈一下被一笔带过的“非常板的轮廓线 dp”是怎么做的。

  • 一般的棋盘形的状压 dp 中,我们记录当前行的状态,并逐行转移。但事实上,逐行设计的状态有一个显著的...缺陷?:转移复杂度较高,有时甚至达到 \(sta^2\) 的规模,令人难以接受。

  • 一个想法是将逐行转移变为逐格转移(我们认为行和列平等)。具体地,将状态转为 \(dp_{i,j,sta}\),这里的 \(sta\) 是当前的轮廓线上的状态。所谓轮廓线,就是当前已经处理完的部分(前 \(i-1\) 行和第 \(i\) 行的前 \(j\) 格)构成的连通块的“暴露在外”的部分(第 \(i\) 行的前 \(j\) 格和第 \(i-1\) 行的第 \(j+1\sim m\) 格),显然其长度为 \(m\)

  • 于是转移时只用考虑左边和上边,转移复杂度可以认为是 \(O(1)\) 了,我们通过多支付一个 \(m\),消掉了转移复杂度。

  • 具体地,在本题中其是如下的 dp 设计:

    • 状态设计:\(dp_{i,j,sta}\) 表示处理完 \((i,j)\),当前轮廓线上是否有插头的方案数。所谓插头就是竖着的上半骨牌(需要在下面接半个)。

    • 初始化:\(dp_{0,m,0}=1\),这里行的范围是 \(1\sim n\),列的范围是 \(0\sim m\)

    • 状态转移:有插头则处理插头,否则可以横转,竖转或空着。具体的式子涉及到 \(sta\) 的变化比较麻烦不写了,不管是滚动 \(sta\) 还是固定 \(sta\)\(sta\) 的第 \(0\) 位和轮廓线上的第 \(0\) 处对齐/总是和当前行的 \(0\) 列对齐)的式子写起来都挺麻烦。

  • 做一遍的复杂度为 \(O(nm2^m)\)。更具体的分析在容斥那边。

  • 哦对,依赖是障碍(和骨牌的性质?不能叠放),需求是跨线。

P4262 [Code+#3] 白金元首与莫斯科

  • 题意略。其实就是把多米诺那道题的 dp 部分单独摘出来,发一个“每个地方如果是障碍”的问罢了。

  • 容易想到正反 dp 然后往一块拼,拼的时候需要两者对特殊格都无插头且所有插头对上,哦那连 FMT 都不用,总复杂度 \(O(nm2^m)\),随便过。

  • 固定 \(sta\) 比较方便因为合并时不用算 \(sta\) 关系了。

插头 dp

  • 典型代表如 \(\text{P5056 [模板]插头 dp}\)\(\text{P3886 [JLOI2009]神秘的生物}\) 等。

  • 特点是(我写这个的时候还只见过棋盘式)需要维护棋盘(也称网格)上的,与连通性相关的某种放置问题。

    • 我对插头 dp 的定义是严格的,即必须有连通性相关限制,否则只认为是轮廓线 dp,即一般状压的一个 trick。
  • 状态设计通常基于最小表示法或括号表示法。

  • 初始化通常为 \(dp_{0,m,0}=1\),这里的后一个 \(0\) 表示状态为空状态,\(1\) 表示其可行。

  • 状态转移需要关于插头情况分类讨论。具体的话...还是看下面的模板题吧。

  • 复杂度通常为 \(O(nmsta)\),这里 \(sta\) 随题目而变化,但之所以这样记主要是因为往往有效状态数远不及开的状态数。

P5056 【模板】插头 dp

  • 题意:给出一张 \(n\times m\) 的棋盘,在其中给定的一些格上铺线(四连通,度为 \(2\),下略)构成一个闭合回路,求方案数。

  • 数据范围:\(n,m\leqslant 12\)。请记住这个标志性的数据范围。

  • 本题是棋盘回路问题的例题。

  • 在一般的轮廓线 dp 中,我们只用记录轮廓线上的插头;但在插头 dp 中我们还得记录它们的连通情况,以确保其连通成一个闭合回路。

  • 怎么记录?主要有两种方法:最小表示法和括号表示法。事实上,两者也指向不同的 dp 设计。

  • 最小表示法:

    • 最常用的方式是,将障碍格标为连通块 \(0\),然后取第一个非障碍格为连通块 \(1\),以此类推,染色。

    • 另一种不太常见的方式是,取所在连通块的最左列(如果从第 \(0\) 列开始,当然要 \(+1\))为连通块标号。这一做法的优点是可以更有效地记录连通块的信息,在某些需要延迟结算的时候有一定补益。注意,在可能有多个同列连通块的时候,这也许需要一些特别处理,比如按左上角编码。

  • 注意到没有开插头的点的连通性没啥用,对以后的点的连通性没有影响,于是可以将没有下插头的点的连通性都当成 \(0\) 来处理;事实上这代表着我们可以把插头情况那个 \(sta\) 直接砍掉,留下的连通情况中的 \(0\) 即为无插头,否则为插头所属的连通块。

    • 应当指出最后一个点的右插头也算开插头,也在轮廓线中,即这里的轮廓线长度为 \(m+1\)
  • 那么到这里我们有了一个 dp:

    • 状态设计:\(dp_{i,j,sta}\) 表示处理完 \((i,j)\),当前轮廓线上的 \(n+1\) 个插头的连通性为 \(sta\)(现在我们还没有将其编码)的方案数。

    • 初始化:\(dp_{0,m,0}=1\),这里最后一个 \(0\) 表示的是全是 \(0\) 的一个零元状态。认为行的范围为 \(1\sim n\),列的范围为 \(0\sim m\),下略。

    • 状态转移:无非三种情况:创建连通块,合并连通块,扩张连通块。

      • 首先应当指出一件事,即为了实现的简单起见,状态内的连通块标号一定是从 \(0\) 开始的一段自然数。在旧有的某个连通块达成回路时,我们可以把它的标号回收(显然开垃圾桶是没法做到的,那样实现的话,相同的状态可能有不同的垃圾桶。我们将 \(sta\) 扫一遍重新标号),因为其必然已经合法(禁止不合法转移),且对以后的过程无影响。

      • 创建连通块:即右、下插头。这一转移显然是 \(O(1)\)(先不考虑状态编码问题)。

      • 合并连通块:即左、上插头。这一转移显然是 \(O(m)\),因为要整理状态。

      • 扩张连通块:另外四种都算,显然是 \(O(1)\)

      • 应当指出,在这里我们的左和上插头情况是已知的,故转移中只有至多两种可能,复杂度可以直接视为 \(O(m)\)

  • 好了。现在我们处理一个比较核心的问题:状态编码。

    • 容易想到进制哈希。思考容易发现任意时刻的非 \(0\) 连通块至多有 \(\lfloor\frac{m}{2}\rfloor\) 个,形如一个极向下的,多叉的凹字形,不可能更多因为上左右三种插头至少要有一种,上插头的话所在连通块一定还有另一个端口,左右插头自然是有一个匹配的端口,故 \(\lfloor\frac{}{2}\rfloor\)

    • 于是只要将进制 \(M\)\(\lfloor\frac{m}{2}\rfloor+1\)(还有 \(0\) 呢)即可,可以考虑上取整到较近的二的整数次幂。

    • 接下来我们需要一个高效的可遍历哈希表。u_map 的效果...啧,考虑一下手写吧。应当指出的是,因为我们共有 \(nm\) 个阶段,每个阶段的合法 \(sta\)
      都是无关的(在从它出发做转移的时候已经确定了,可以认为是无关),故不妨拿两个哈希表滚维,这样一来哈希表中的元素数量少得多,在实践中优秀不少。

    • 注意到无用状态可能非常多。故考虑舍弃一般的循环转移形式,改做分层 bfs。

  • 综上所述,若忽略哈希表的访问复杂度,则状态数为 \(O(nmsta)\)\(sta\) 远远不满,可以考虑先写一个搜看看状态数),转移复杂度为 \(O(m)\)(哈希表的编码/解码复杂度,记得将 \(M\) const 化),总复杂度为 \(O(nm^2sta)\),若认为 \(n,m\) 同阶则为 \(O(n^3sta)\)

  • 括号表示法:

    • 在刚才计算进制的时候,我们已经注意到每个连通块事实上都是一个有两个端口的路径,显然这两个端口都在轮廓线上(否则不合法)。

    • 大量打表可以看出,如果按从左到右的顺序列出(将右插头列在左右两个下插头之间),插头总体上呈一种括号序列的匹配关系,或者说,若将同连通块的插头视为一对左右括号,则其是一个合法括号序列。

    • 事实上,我们有定理:对从左到右的四个插头 \(a,b,c,d\),若 \(a,c\) 连通且不与 \(b\) 连通,则 \(b,d\) 也不连通。

    • 不妨使用反证法。否则两者的路径一定在上方某处相交,则要么交点的插头数不合法,要么两者是一个连通块。

    • 之所以特别要求不与 \(b\) 连通,是因为可能有 \(b,c\) 之间的横向插头。

    • 于是考虑将插头当做括号来编码,记无插头为 \(0\),左括号为 \(1\),右括号为 \(2\),只需要做三进制状压(实践中当然是四进制)。

  • 故有如下 dp 设计:

    • 状态设计:\(dp_{i,j,sta}\) 表示处理完 \((i,j)\),当前轮廓线上的 \(n+1\) 个插头的括号性为 \(sta\) 的方案数。

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

    • 状态转移:还是三种情况。

      • 方便起见,记当前从 \((i,j)\) 转移到 \((i,j+1)\),记 \((i,j)\) 的右插头为 \(p\)\((i-1,j+1)\) 的下插头为 \(q\)\(p',q'\)\((i,j+1)\) 的右插头和下插头(不存在则视为 \(0\) 插头),\(plug=0/1/2\) 表示插头状态。

      • 创建连通块:即右、下插头。需要 \(p=q=0\),得到 \(p'=2,q'=1\)(注意右插头反而是括号性序列上较靠后的),\(O(1)\)。注意必须不在右下边界才能这样转移。

      • 扩张连通块:即左或上插头+右或下插头。需要 \(p\)\(q\) 中恰有一者为 \(0\)。此时直接继承对应插头的括号性即可。

      • 合并连通块:即左、上插头。此时 \(p'=q'=0\),但其他插头的括号型可能要稍作修改:

        • \(p=q=1\)\(match(q)=1\)

        • \(p=1,q=2\):由括号性,两者恰为一对匹配的括号。此时该连通块结束,不需修改。在本题中,这只在最后一个非障碍格可行。

        • \(p=2,q=1\):合并后 \(match(p)\)\(match(q)\) 仍然恰匹配,不需修改。

        • \(p=q=2\)\(match(p)=2\)

        • 两个 \(O(m)\)(找匹配所耗时间),两个 \(O(1)\)。事实上可以直接预处理好匹配关系。

  • 应当指出还是要使用 bfs,因为很多 \(sta\) 不是一个合法括号序列。被卡空间...考虑使用 u_map 或者三进制(注意两者都是时间换空间)?应该不会吧,毕竟可以把 \(i,j\) 两维都滚掉。upd:本题就卡空间,我三进制过的。

  • 状态数 \(O(nmsta)\),预处理匹配关系复杂度 \(O(m3^{m+1})\),转移复杂度 \(O(1)\),总复杂度为 \(O(m3^{m+1}+nmsta)\)。事实上,实践中括号表示法的表现要比最小表示法的优越不少。

P5074 Eat the Trees

  • 题意:上一题的单回路改为多回路。

  • 啊这岂不是我把刚才的改改就能过吗...把 \(p=1,q=2\) 的限制放宽,不强制最后一个非障碍格...

  • 你说得对,但是 \(\text{P5074 Eat the Trees}\) 是由 HDU 自主研发的一道轮廓线 dp 模板题。题目发生在一个需要多个闭合回路的棋盘上,在这里,被选中的插头将无视括号的左右性地构成回路,因为左右性是用来防止回路提前闭合的!!!

  • 仔细思考会发现,这里括号的左右性和实际方案有关,但和我们的方案数却完全无关。故可以做一个简单的轮廓线 dp,类似白金元首但有一个右插头,也可以说是丐版插头 dp,复杂度 \(O(nm2^{m+1})\)

POJ 1739 Tony’s Tour

  • 题意:求网格中左下出发到右下并经过所有非障碍格的路径方案数,保证左下和右下不是障碍。

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

  • 本题有一个取巧的解法:在给出的网格外套两层(下边界不动),第一层除了和起止点相邻的点外全是障碍,第二层没有障碍,问题变成求回路方案数。

HDU 1964 POJ 2064 NWERC 2004 Pipes

  • 题意:求最小费用单回路。输入中的 # 可以认为是无用字符,滤掉就好。

  • 那么也就是个板子罢了。

P2289 [HNOI2004] 邮递员

  • 题意:求从左上角出发的最短全遍历网格回路数,注意是有向的回路。数据在事实上保证了 \(n\times m\) 为偶数。

  • 数据范围:\(n\leqslant 20,m\leqslant 10\)

  • 这很 TSP,但数据范围显然不这么想。考虑插头 dp,盲猜访问一个点两次是不优的,并盲猜只会相邻转移。

  • 对吗?首先,对于 \(n=1 \text{ or } m=1\),需要特判。然后,当 \(n\times m\) 为奇数,这不太对,譬如在 \(3\times 3\) 的时候一定会斜着走一步,最短回路为 \(8+\sqrt{2}\)

  • 不过倒是足够做这道题了。复杂度 \(O(m3^{m+1}+nmsta)\),很稳。

  • 如果 \(n\times m\) 为奇数:考虑暴力枚举斜着走的那一步,于是问题变成网格路径计数,多一个枚举的 \(O(nm)\)。不过网格路径问题...我们还要在后面才谈到。

P3190 [HNOI2007] 神奇游乐园

  • 题意:求网格中的最大权闭合回路,要求回路长度 \(\geqslant 4\)(即不能是一个单点)。

  • 注意到直接照搬管道和邮递员的做法可能会导致回路从未开始。解决办法:在闭合回路时对 \(ans\) 转移,放弃从 \(dp_{now,0}\)\(ans\)

  • 时间复杂度 \(O(m3^{m+1}+nmsta)\),非常宽松。啊既然这么宽松我们来写一个四进制状态?感觉我不是很会写四进制状态...已经适应三进制状态了...那就三进制吧...

网格简单路径计数

  • 考虑将模板题中的简单回路推广到任意起点路径,即网格路径计数(经过所有非障碍点)。怎么做?

  • 考察我们之前用到而现在没有的性质,发现主要是“每个点的度都恰为 \(2\)”,现在起点和终点的度为 \(1\)

  • 故有一个自然的想法:将单度点的插头作为单独的一类摘出来(下称孤立插头),做四进制状压。更一般化(也更正确)地,不是单度点的插头,而是没有匹配的插头,即一条路径的前缀(这里认为没有方向,故前后缀没有区别)的插头是孤立插头。

  • 具体化地,设计如下 dp:

    • 状态设计:\(dp_{i,j,sta}\) 表示处理完 \((i,j)\),插头情况为 \(sta\) 的方案数。

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

    • 状态转移:和模板题一样的就不再赘述,只说多出的转移(沿用 \(p,q,p',q'\) 的记号,并记孤立插头为 \(3\)):

      • 创建孤立插头:\(p=q=0\) 时可以 \(p'=0,q'=3\)\(p'=3,q'=0\)

      • 扩张孤立插头所在连通块:\(p=3,q=0\)\(p=0,q=3\),基本没有什么区别,还是继承括号性。

      • 合并孤立插头和其他插头(对称情况省略):

        • \(p=1/2,q=3\)\(match(p)=3\)

        • \(p=q=3\):该连通块结束。这一转移只在最后一个非障碍格可行。

  • 容易证明区分左,右,孤立插头是必要的,因如果不区分孤立插头则会是任意条路径,而区分左右插头是为了正确维护孤立性。

  • 预处理匹配情况,有总复杂度为 \(O(m4^{m+1}+nmsta)\)

UVA10531 Maze Statistics

  • 题意:
    • 称一个生成网格图合法,当且仅当其中存在至少一条 \((1,1)\to (n,m)\) 的路径。

    • 给出一张网格图中每个点变成障碍物的概率。记 \(S\) 为合法生成网格图集合,则 \(\forall s\in S,P(s)=\frac{\prod\limits_{(i,j)\in s} p((i,j))}{P(S)}\),这里 \(P(S)\) 是生成一张合法网格图的概率。

    • 对每个点 \((i,j)\),求 \(\sum\limits_{s\in S \land (i,j)\in s} P(s)\),这里 \((i,j)\in s\) 表示 \((i,j)\)\(s\) 中是障碍物。

posted @ 2023-01-12 21:31  未欣  阅读(104)  评论(0编辑  收藏  举报