DP 套 DP

动态规划 / dp-of-dp

本地源文件:docs/dp__dp-of-dp.md

DP 套 DP

引入

本文将介绍 DP 套 DP 的思想,并通过两道例题展示它如何应用到具体问题中.

思想

所谓「DP 套 DP」,实际上指的是在动态规划的过程中,把一个子问题的求解过程(通常是一个 DP)抽象成一个自动机(DFA),然后在这个自动机的基础上再设计一层新的 DP 的方法.

这种技巧主要应用于一类 序列计数概率期望 问题.一个典型的问题具有如下结构:

  • 给定字符集 ΣΣ 和它上面一个长度为 𝑛n 的「合法序列」的集合 𝐴 ⊆Σ𝑛A⊆Σn.根据字符集不同,序列可以是二进制串、数字串、状态序列等.
  • 对于每一个具体的序列 𝑠 ∈Σ𝑛s∈Σn,可以通过动态规划来判断它是否合法(即 𝑠 ∈𝐴s∈A),计算它的权值或求出相关值.
  • 最终,我们希望统计集合 𝐴A 中所有序列的数目、总权值、期望值等.

这时,枚举所有序列是不可行的.于是我们考虑将「判断一个序列是否合法」的过程(即内层 DP)抽象为一个 确定有限状态自动机(DFA).一般地,对于固定的序列 𝑠 ∈Σ𝑛s∈Σn,内层 DP 的状态函数可以表示为 𝑔(𝑖,𝑥;𝑠)g(i,x;s),即已经处理完序列 𝑠s 的长度为 𝑖i 的前缀,且其他状态分量为 𝑥x 时某个量的取值.相应地,内层 DP 的状态转移方程为

𝑔(𝑖,⋅;𝑠)=𝐺(𝑔(𝑖−1,⋅;𝑠),𝑠𝑖).g(i,⋅;s)=G(g(i−1,⋅;s),si).

也就是说,函数 𝑔(𝑖, ⋅;𝑠)g(i,⋅;s) 由之前的函数 𝑔(𝑖 −1, ⋅;𝑠)g(i−1,⋅;s) 和当前的字符 𝑠𝑖si 唯一确定.如果将函数 𝑔(𝑖, ⋅;𝑠)g(i,⋅;s) 看作是自动机的一个状态,那么,内层 DP 的状态转移方程就给出了自动机的一个转移.因此,内层 DP 对应的自动机 (𝑄,Σ,𝛿,𝑞0,𝐹)(Q,Σ,δ,q0,F) 结构如下:

  • 状态集合 𝑄Q 就是所有可能的 𝑠 ∈Σ𝑛s∈Σn 和 𝑖 =0,1,⋯,𝑛i=0,1,⋯,n 对应的函数 𝑔(𝑖, ⋅;𝑠)g(i,⋅;s) 的集合;
  • 转移函数 𝛿 :𝑄 ×Σ →𝑄δ:Q×Σ→Q 就是内层 DP 的状态转移方程中的 𝐺G
  • 起始状态 𝑞0q0 通常是显然的,即内层 DP 的初始状态;
  • 接受状态集合 𝐹F 就对应着所有的合法序列 𝑠 ∈𝐴s∈A

函数 𝑔(𝑖, ⋅;𝑠)g(i,⋅;s) 本身可能相当复杂,因此,在处理具体问题时,通常需要进行 状态压缩 或结合 DFA 最小化的技巧来压缩状态空间.这也是 DP 套 DP 相较于暴力 DP 能够显著降低时空复杂度的主要原因.

将内层 DP 抽象为 DFA 后,就可以在这个 DFA 上设计一个新的 DP 用于求解原问题,即外层 DP.为方便表述,以简单的计数问题为例.外层 DP 的状态函数定义为 𝑓(𝑖,𝑞)f(i,q),即处理到长度为 𝑖i 的前缀时,到达 DFA 中状态 𝑞 ∈𝑄q∈Q 的前缀的数目.它的状态转移方程为

𝑓(𝑖,𝑞)=∑𝑐∈Σ∑𝑞′∈𝑄:𝛿(𝑞′,𝑐)=𝑞𝑓(𝑖−1,𝑞′).f(i,q)=∑c∈Σ∑q′∈Q:δ(q′,c)=qf(i−1,q′).

起始状态当然是 𝑓(0,𝑞0)f(0,q0),而最终要求的答案通常可以根据 {𝑓(𝑛,𝑞) :𝑞 ∈𝐹}{f(n,q):q∈F} 简单计算得到.外层 DP 实际上是 DAG 上 DP 的特殊情形.

例题

接下来的两个例题会详细说明 DP 套 DP 的一般做法.

例一

Hero meet devil

给定一个字符集为 ACGT 的字符串 𝑆S,且 |𝑆| ≤15|S|≤15.对于每个 0 ≤𝑖 ≤|𝑆|0≤i≤|S|,求有多少个长度为 𝑚m,字符集 ACGT 的字符串 𝑇T,满足它与 𝑆S 的最长公共子序列长度为 𝑖i

题解

我们首先会想到一个 DP:设 𝑓𝑖,𝑗fi,j 表示长度为 𝑖i 的 𝑇T 中,和 𝑆S 的最长公共子序列的长度为 𝑗j 的方案数.但是这样无法转移,我们发现主要的问题是,我们不知道这个最长公共子序列对应的是哪些字符.

考虑朴素求最长公共子序列的过程.设 𝑔𝑖,𝑗gi,j 表示 𝑇T 的前 𝑖i 位和 𝑆S 的前 𝑗j 位,它们的最长公共子序列的长度,就有

𝑔𝑖,𝑗=max{𝑔𝑖−1,𝑗,𝑔𝑖,𝑗−1,𝑔𝑖−1,𝑗−1+[𝑇𝑖=𝑆𝑗]}.gi,j=max{gi−1,j,gi,j−1,gi−1,j−1+[Ti=Sj]}.

我们发现,对于一个 𝑖i,只需要记录 𝑔𝑖gi 这个一维数组每一位的值,就能准确维护当前 𝑆S 与 𝑇T 前 𝑖i 位最长公共子序列的状态.因为 𝑆S 长度只有 1515,我们发现这个思想是可行的.

于是重新设状态 𝑓𝑖,𝑥fi,x 表示对于长度为 𝑖i 的 𝑇T,与 𝑆S 的 DP 数组(就是 𝑔𝑖gi)状态为 𝑥x 的方案数.这个 DP 看起来状态数很多,然而我们发现 𝑔𝑖,𝑗 −𝑔𝑖,𝑗−1 ∈{0,1}gi,j−gi,j−1∈{0,1},就可以维护 𝑔𝑖gi 的差分数组,状态数是 2|𝑆|2|S| 的.

现在思考怎么转移.容易发现,如果我们知道了 𝑔𝑖gi 这个数组,也知道了 𝑇𝑖+1Ti+1,就能通过朴素 LCS 转移(即前文的 DP 方程)求出 𝑔𝑖+1gi+1.于是朴素的 LCS 就成为了帮助 𝑓f 转移的内层 DP.

因此,我们枚举 𝑇𝑖+1Ti+1,计算出 𝑥x 转移后的状态 𝑥′x′,再将 𝑓𝑖+1,𝑥′fi+1,x′ 加上 𝑓𝑖,𝑥fi,x 就可以完成外层 DP 的状态转移.最后,我们记录 𝑎𝑛𝑠𝑖ansi 为 LCS 长度为 𝑖i 的答案,枚举每个状态 𝑆S,𝑎𝑛𝑠popcount⁡(𝑆)anspopcount⁡(S) 加上 𝑓𝑚,𝑆fm,S 即可.

参考代码

---|---

### 例二

[[ZJOI2019] 麻将](https://loj.ac/p/3042)

假设麻将牌有 𝑛n![](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7) 种大小的牌,每种大小有 44![](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7) 张牌.定义面子为三张相邻大小的麻将牌 𝑖,𝑖 +1,𝑖 +2i,i+1,i+2![](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7)(顺子)或三种相同大小的麻将牌 𝑖,𝑖,𝑖i,i,i![](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7)(刻子),对子为两张相同大小的麻将牌 𝑖,𝑖i,i![](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7).定义一个麻将牌的序列是胡的,当且仅当它(看作多重集合)可以拆成四个面子和一个对子,或者七个不同的对子.给定 1313![](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7) 张麻将牌,问期望再摸多少张牌可以满足存在一个胡牌的子序列的条件.

题解

首先,对于一副牌,我们只需考虑每种牌的数量,而不必关心它们的顺序.因此,对于任何一副牌的任意前缀,我们都可以将它转化为一个长度为 𝑛n![](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7),每个位置上为 0 ∼40∼4![](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7) 的序列.初始时,第 𝑖i![](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7) 张牌的数量为 𝑎𝑖ai![](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7),就相当于限制了序列中第 𝑖i![](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7) 个数字 𝑥𝑖xi![](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7) 的取值为 [𝑎𝑖,4][ai,4]![](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7) 内的整数.注意,转化后的序列没有考虑摸牌的顺序,但是题目中的麻将牌的序列是考虑顺序的.

设 𝑋X![](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7) 表示可以胡牌的最小摸牌次数.直接计算期望 𝐄[𝑋]E[X]![](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7) 较为困难,可以考虑进行如下转化.设 ℎ𝑖hi![](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7) 表示摸了 𝑖i![](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7) 张牌后 **没有胡** 的序列数.因为它对应的麻将牌序列中,这 𝑖i![](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7) 张牌必然排在剩下的 (4𝑛 −13 −𝑖)(4n−13−i)![](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7) 张牌前方,但是这 𝑖i![](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7) 张牌和剩下的 (4𝑛 −13 −𝑖)(4n−13−i)![](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7) 张牌的顺序是任意的,所以,只摸前 𝑖i![](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7) 张牌无法胡牌的麻将牌序列的数目就是

ℎ𝑖⋅𝑖!(4𝑛−13−𝑖)!.hi⋅i!(4n−13−i)!.![](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7)

因为麻将牌序列的总数为 (4𝑛 −13)!(4n−13)!![](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7),所以,只摸前 𝑖i![](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7) 张牌无法胡牌的概率为

𝐏[𝑋>𝑖]=ℎ𝑖⋅𝑖!(4𝑛−13−𝑖)!(4𝑛−13)!.P[X>i]=hi⋅i!(4n−13−i)!(4n−13)!.![](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7)

利用尾求和公式,就可以得到所求期望

𝐄[𝑋]=∞∑𝑖=0𝐏[𝑋>𝑖]=1+4𝑛−13∑𝑖=1ℎ𝑖⋅𝑖!(4𝑛−13−𝑖)!(4𝑛−13)!.E[X]=∑i=0∞P[X>i]=1+∑i=14n−13hi⋅i!(4n−13−i)!(4n−13)!.![](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7)

至此,问题转化为如何计算 ℎ𝑖hi![](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7).我们采用 DP 套 DP 的方法来解决这一问题.

首先,考虑内层 DP,也就是要用动态规划判断一个(转化后的)序列是否对应一副能胡的牌.七对子的情形较为容易,重点讨论第一种胡牌的形式.为此,设 𝑔0/1,𝑖,𝑗,𝑘g0/1,i,j,k![](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7) 表示处理完前 𝑖i![](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7) 种牌,还剩 𝑗j![](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7) 组 (𝑖 −1,𝑖)(i−1,i)![](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7) 以及 𝑘k![](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7) 张 𝑖i![](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7),且存在/不存在对子(即 0/10/1![](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7))时最多的面子数.如果对于一个序列进行 DP,最后得到的 𝑔1,𝑛g1,n![](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7) 中包括一个大于等于 44![](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7) 的数字,那么这个序列就是能胡的.

这个 DP 的状态转移较为复杂.我们分两步讨论.第一步,考虑 𝑔0/1,𝑖g0/1,i![](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7) 的转移.这相当于说,如果要向现在的牌型中,添加 𝑥𝑖xi![](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7) 张大小为 𝑖i![](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7) 的牌,但是不组成新的对子时,面子数如何转移.显然,如果希望在添加 𝑥𝑖xi![](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7) 张大小为 𝑖i![](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7) 的牌后,要得到 ℓℓ![](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7) 个顺子,𝑗j![](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7) 个 (𝑖 −1,𝑖)(i−1,i)![](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7) 和 𝑘k![](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7) 个单独的 𝑖i![](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7),那么,就应该从 (𝑔0/1,𝑖−1)ℓ,𝑗(g0/1,i−1)ℓ,j![](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7) 中转移过来(这里的选择最大程度避免了浪费),并将剩余的牌 (𝑥𝑖 −ℓ −𝑗 −𝑘)(xi−ℓ−j−k)![](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7) 用于组成尽可能多的刻子.穷举所有的可能性,就得到如下转移方程:

˜𝐺(𝑔0/1,𝑖−1,𝑥𝑖)𝑗,𝑘=max{(𝑔0/1,𝑖−1)ℓ,𝑗+ℓ+⌊𝑥𝑖−ℓ−𝑗−𝑘3⌋:ℓ+𝑗+𝑘≤𝑥𝑖}.G~(g0/1,i−1,xi)j,k=max{(g0/1,i−1)ℓ,j+ℓ+⌊xi−ℓ−j−k3⌋:ℓ+j+k≤xi}.![](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7)

第二步,再考虑需要组成对子的情形.加入 𝑥𝑖xi![](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7) 张大小为 𝑖i![](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7) 的牌时,有如下三种转移:

  * 将 𝑔0,𝑖−1g0,i−1![](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7) 加 𝑥𝑖xi![](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7) 张牌转移到 𝑔0,𝑖g0,i![](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7);
  * 将 𝑔1,𝑖−1g1,i−1![](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7) 加 𝑥𝑖xi![](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7) 张牌转移到 𝑔1,𝑖g1,i![](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7);
  * 若 𝑥𝑖 ≥2xi≥2![](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7),将 𝑔0,𝑖−1g0,i−1![](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7) 加 𝑥𝑖 −2xi−2![](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7) 张牌转移到 𝑔1,𝑖g1,i![](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7).

由此,就得到了从 𝑔𝑖−1gi−1![](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7) 添加 𝑥𝑖xi![](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7) 张牌得到 𝑔𝑖gi![](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7) 的全部转移.

解决了内层 DP 的状态转移,就可以构建 **胡牌自动机** .自动机的转移就是上述内层 DP 的转移,还需要考虑如何表示自动机的每个状态.每个状态都对应 𝑔𝑖gi![](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7) 的一种可能的取值.它有三个维度 (0/1,𝑗,𝑘)(0/1,j,k)![](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7).因为 𝑗j![](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7) 和 𝑘k![](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7) 对应的维度中保留的 (𝑖 −1,𝑖)(i−1,i)![](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7) 和 𝑖i![](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7) 都是用于将来组成顺子的,而三个相同顺子总是可以重组为三个刻子,所以,只需要考虑组成不超过 22![](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7) 个相同顺子的需求就行,每种牌型也就只要保留不超过 22![](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7) 个,即 𝑗,𝑘 ∈{0,1,2}j,k∈{0,1,2}![](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7).因此,𝑔𝑖gi![](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7) 可以表示为一个 2 ×3 ×32×3×3![](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7) 的数组.另外,为了维护七对子的胡牌牌型,还需要为每个状态添加一个计数器,用于表示当前最多可组成的对子数目.

数组 𝑔𝑖gi![](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7) 中每个元素的取值范围可能是 { −∞} ∪𝐍{−∞}∪N![](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7),但是,因为面子数目大于等于 44![](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7) 都是胡牌,所以,可以限制每个元素取值不超过 44![](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7).由于胡牌序列再添加任何牌都是胡牌序列,所以,可以利用 DFA 最小化的思想,将全体胡牌状态压缩为一个状态.因此,对于非胡牌状态,实际上每个位置的取值只要考虑 { −∞} ∪{0,1,2,3}{−∞}∪{0,1,2,3}![](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7) 就可以了.实现时,−∞−∞![](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7) 用 −1−1![](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7) 表示.

尽管如此,所有可能的状态依然相当地多,共有 1 +7 ×5181+7×518![](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7) 种.穷举它们并不现实.实际上,绝大多数这些可能性都不会真的出现在一个胡牌自动机中.为了避免考虑实际不存在的状态,可以利用 BFS 的思想,从初始状态开始,一步一步扩展状态,直到胡牌状态处停止.这样得到的自动机中有 𝑁 =2092N=2092![](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7) 个状态.

最后,考虑如何在胡牌自动机上 DP(即外层 DP).设 𝑓𝑖,𝑗,𝑘fi,j,k![](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7) 表示处理到第 𝑖i![](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7) 张牌,共摸了 𝑗j![](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7) 张牌,走到了胡牌自动机上的 𝑘k![](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7) 号状态的序列数.转移时,枚举摸牌数 0 ≤𝑡 ≤4 −𝑎𝑖0≤t≤4−ai![](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7),其中 𝑎𝑖ai![](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7) 为初始 1313![](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7) 张牌中用掉的 𝑖i![](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7) 的张数,将之前的序列数乘以 4 −𝑎𝑖4−ai![](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7) 张牌中选 𝑡t![](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7) 张牌的方案数 (4−𝑎𝑖𝑡)(4−ait)![](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7),再累加到一起.形式化地,有:

𝑓𝑖+1,𝑗+𝑡,𝑘′=4−𝑎𝑖∑𝑡=0(4−𝑎𝑖𝑡)𝑓𝑖,𝑗,𝑘.fi+1,j+t,k′=∑t=04−ai(4−ait)fi,j,k.![](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7)

其中,𝑘′ =𝛿(𝑘,𝑎𝑖 +𝑡)k′=δ(k,ai+t)![](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7),表示向自动机的状态 𝑘k![](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7) 中加入 𝑎𝑖 +𝑡ai+t![](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7) 张牌后的状态.外层 DP 结束后,就可以计算出摸了 𝑖i![](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7) 张牌仍然没有胡牌的序列数目,即

ℎ𝑖=𝑁∑𝑗=1𝑓𝑛,𝑖,𝑗.hi=∑j=1Nfn,i,j.![](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7)

代入前文所述表达式,就可以得到所要求的期望.

参考代码

---|---

习题

本页面最近更新: 2026/1/7 08:56:54,更新历史 发现错误?想一起完善?在 GitHub 上编辑此页! 本页面贡献者:c-forrest, Hope666666, Tiphereth-A, ZnPdCo 本页面的全部内容在CC BY-SA 4.0SATA 协议之条款下提供,附加条款亦可能应用