DP套DP学习笔记

小学一下

当初听到这个东西云里雾里的,真的感觉好逆天啊

本文的格式化全部由 \(\text{AI}\) 完成,真的太强大了

考虑一类问题,你不会,然后打了暴力,发现暴力大概是先在外面枚举状态,然后里面是一个 \(\text{dp}\) 来计数。大概就是这样

void dfs(int x){
    if(x==n+1)return void(ans+=dp());
    for(int i=1;i<=m;i++){
        ...
        dfs(x+1)
        ...
    }
}

我们发现,我们 \(\text{dp}\) 有好多东西都是挺重复的,这个 \(\text{dp}\) 可能只和某些变量有关,不必要求出所有东西,也不必要知道所有东西,既然有冗余的信息,那么我们外层再套一层 \(\text{dp}\) 就行了

这里其实很难理解,我们从一些例题来理解

[bzoj3864 Hero meet devil]

给定一个字符串 \(S\) 和一个数字 \(m\) ,求有多少个长度为 \(m\) 的字符串 \(T\) ,使得 \(S\)\(T\) 的最长公共子序列长度恰好为 \(k\)

我们正常的搜索是枚举所有字符串,然后里面一个 \(\text{dp}\) 算一下最长公共子序列的长度,然后外面搞一下

首先深入理解一下最长公共子序列的 \(\text{dp}\)\(dp[i][j]\) 为前 \(i\)\(j\) 的最长公共子序列,并没有什么限制。

关键观察!我们在外面构造 \(T\) 时,也就是我们 \(dfs\) 不断做下去的时候,我们已经知道了前 \(i-1\) 行的所有值,现在我们要往 \(dp[i][j]\) 转移,我们发现,我们不关心 \(i-2\) 行之前的值,我们只关心最后一列。发现最后一列的差分数组只会相差 \(0/1\) ,所以我们直接存一个 \(01\) 串就好了!

所以我们设 \(dp[i][01……01010]\) 为做到 \(i\)\(\text{dp}\) 最后一行为这个东西的方案数

现在考虑转移。

枚举所有状态,现在我们已经知道了 \(j\) 和状态,我们枚举下一个字符,然后算一下新的状态,然后直接贡献过去,最后加起来即可

P4590

有两个字符串 \(S\) , \(T\) ,其中 \(T\) 给定,让你求有多少个长度为 \(n\)\(S\)\(T\) 的最长公共子序列为 \(0\) ~ \(k\) ,其中 \(S\) 不能连续出现 \(NOI\)

首先考虑这题的暴力,其实和上面相同

然后我们还是有 \(\text{dp}\) 方程 \(dp[i][0/1/2][0101....1101]\) ,第一维代表当前匹配到了第 \(i\) 位,第二个代表 \(S\) 匹配到了 \(NOI\) 的哪一位,最后一个就是 \(\text{dp}\) 状态

P8352

挑战

这个东西放在树上了,不难想到可以放在树上搞 \(\text{dp}\)

首先我们会怎么做最大独立集,我们会有一个 \(\text{dp}\)\(dp[i][0/1]\) 为以 \(i\) 为根的子树,选/不选根的最大独立集大小。

所以我们的 \(\text{dp}\) 方程之和两个有关啊!

\(dp[i][j][k]\) 为以 \(i\) 为根的子树最大独立集答案分别为 \(j\)\(k\) 的答案的方案数

这样有些困难。但是我们怎么?我们发现我们最大独立集的答案之间不会超过 \(k\) ,但是这个东西在我们上面这个 \(\text{dp}\) 方程的意义下好像比较难

修改求最大独立集大小 \(\text{dp}\) 方程的意义变成 \(dp[i][0]\) 是一定不选, \(dp[i][1]\) 是选不选都可以

那么选不选都可以肯定不会比 \(dp[i][0]\) 大超过 \(k\) ,一旦大了超过 \(k\) ,那么肯定不合法。

\(dp[i][t1][t2]\) 为以 \(i\) 为根的子树最大独立集答案为 \(t1\) , \(t1+t2\) 的答案,

怎么转移?

首先 \(dp[i][0][k]=1\)

\(dp[i][0]+=dp[to][1]\)

\(dp[i][1]+=dp[to][0]\)

这个不重不漏是显然的

然后树形背包转移,枚举当前的 \(t1\) , \(t2\) ,枚举一下下面的 \(t1\) , \(t2\) ,然后判下合不合法,然后就好了吧
\(dp[i][t1][t2]=dp[i][t1][t2]+dp[j][t1]\)

P5279

挑战

给你起手,假设你有预知未来的能力,问你最早胡牌巡目数

胡牌一种是正常的,一种是七对子,考虑将两个期望分开来求

我们现在求有多少种情况满足,也就是有多少个排列使得加上末尾的一张牌刚好胡牌

也就是我们求 \(\sum f(i)\) 为刚好在 \(i\) 回合胡牌的情况数,我们现在求 \(g(i)\) 为前 \(i\) 个回合胡牌的情况数?这个是有顺序的啊满足胡牌。现在就是要计数,有多少个集合大小为 \(i\) 的子集满足加上最初的集合为胡牌集合

这个感觉很好求啊?

嘿嘿,我只会 \(4^{13}\) 好好好,不过我们已经搞掉了 \(13\) 张牌


上面的题解是没有学 \(\text{dp}\)\(\text{dp}\) 时候写的,现在继续思考。

这题对 \(\text{dp}\)\(\text{dp}\) 的理解需要更深入一点

还记得 \(AC\) 自动机上 \(\text{dp}\) 吗?我们 \(\text{dp}\) 的有一个状态就是自动机的节点,然后我们转移的过程中我们就直接去 \(\text{dp}\) 了。这其实和 \(\text{dp}\)\(\text{dp}\) 的本质是相同的。

以求最长公共子序列的那个东西为例子,我们其实 \(\text{dp}\) 方程里面也记录了状态,然后根据当前的状态来判断当前的最长公共子序列。然后 \(\text{dp}\) 方程里面的状态就是根据里面的状态进行转移。

也就是说, \(AC\) 自动机上 \(\text{dp}\) ,我们内层的自动机是显式的,而 \(\text{dp}\)\(\text{dp}\) ,我们内层的自动机通常是隐式的。


考虑一个集合,我们可以将它转化为一个桶,显然是对的。然后我们建立胡牌自动机,接受一个桶,然后判断是否胡牌。

我们先建立一个 \(\text{dp}\) ,然后把它改成自动机的形式。所以现在我们思考 \(\text{dp}\) 和自动机之间的联系。我有两种想法,一种是把 \(\text{dp}\) 的状态作为自动机的状态,这样 \(\text{dp}\) 中的值是一种超脱自动机的东西。还有一种是把 \(\text{dp}\) 的状态和它的值作为一个键扔进自动机里面,这两种都可以。

所以说, \(\text{dp}\) 和自动机是相生的!

我们发现我们的桶编号是连续的,这样就会有一些不错的性质。

首先判断一下七对子,这个咋判?硬要上的话其实也是可以的。设 \(dp[i][j][k]\) 为做到编号为 \(i\) 的牌了,前面有 \(j\) 个面子, \(k\)\(i-2\) , \(i-1\) ,有没有对子,

大力写一下转移方程还是简单的,但是不是太不优美了
\(dp[i][j][k][1]=dp[i-1][j][k][]\)

我们这个 \(\text{dp}\) 方程还有问题,也就是说我们没有给 \(i-2\) , \(i-1\) , \(i\) 的这种留下开头。然后因为这个东西还是判定性 \(\text{dp}\) ,注意到其中的一维是单调的,所以我们直接压缩一下状态。把面子的个数去掉。求当前能组成的最大面子数。

\(dp[i][j][k][0/1]\) 为当前做到编号为 \(i\) 的牌了,有 \(j\)\(i-1\) , \(i\) 形式的东西, \(k\)\(i\) 作为 \(i\) , \(i+1\) , \(i+2\) 的开头,是否预留了一个对子。

然后就好了。我们的 \(j\) , \(k\) 都不超过 \(2\) ,因为我们 \(3\)\(i-2\) , \(i-1\) 可以算作 \(3\) 个面子。

然后就可以大力转移了!

把这个扔到自动机里面,发现第一维什么用都没有,扔掉,

假设我们新的一位桶为 \(0\)

\(dp[i][0][0][0]=\max(dp[i-1][j][k][0])\)
\(dp[i][0][0][1]=\max(dp[i-1][j][k][1])\)

\(1\)

\(dp[i][1][0][0]=\max(dp[i-1][j][1][0])\)
\(dp[i][0][1][0]=\max(dp[i-1][j][k][0])\)
\(dp[i][0][0][0]=\max(dp[i-1][j][k][0])\)
\(dp[i][1][0][1]=\max(dp[i-1][j][1][1])\)
\(dp[i][0][1][1]=\max(dp[i-1][j][k][1])\)
\(dp[i][0][0][1]=\max(dp[i-1][j][k][1])\)

\(2\)

发现真的太烦了可否进一步减少分讨

\(1\) . 直接贡献给面子 \(x>=3\)
\(2\) . 直接贡献给对子 \(x>=2\)
\(3\) . 作为开头 \(x>=1\)
\(4\) . 作为第二个
\(5\) . 作为第三个

你的 \(\text{dp}\) 还是太烦了。

注意到,我们的 \(\text{dp}\) 方程是求最大值,所以有些情况我们根本不用枚举,因为很多都是重复或者不优的。

为了达到最优,我们肯定希望让每张能贡献的牌尽量多贡献一点。所以我们钦定去贡献给 \(i-2\) , \(i-1\) 这些的东西,剩下的就是面子,所以我的分讨

我们肯定优先

枚举上面一个的状态,然后直接搞即可,应该不是特别麻烦。

我们把 \(dp[j][k][0/1]\) 扔到自动机里面,然后再加一维代表当前最多有多少个对子,这个东西是不会影响 \(\text{dp}\) 的事的。


期望 \(\text{dp}\) 有一个经典的套路,也就是对于求这种恰好大小为 \(i\) 胡了的情况数,我们不仅可以二项式反演,而且可以算贡献,详见下面

我们的答案是 \(\frac{\sum_{i=0}^{4n-13} g(i)i!(4n-13-i)!}{(4n-13)!}\)

有些题解下标是从 \(i=1\) 开始的,这也没有关系

其中 \(g(i)\) 的意义是已经摸了 \(4n-13\) 张牌了,我们还没有胡牌的情况数。然后后面是可以随便乱填的,然后前面因为我们算的是集合,所以我们还要算上一个 \(i!\)

关于大小相同的牌算不算相同的,龙老师有言:认为所有牌都是不同的是没有错的!也很好理解了。这题我们把大小相同的牌看作了不同的,这相当于分子和分母都同时多算了一些,所以这个 \(\text{dp}\) 就是对的

现在转化成了求 \(g(i)\)

\(dp[i][j][k]\) 为当前做到第 \(i\) 张牌了,已经摸了 \(j\) 张牌了,走到了自动机的节点 \(k\) 的方案数。

所以我们现在考虑直接枚举桶的上的每一位,然后不停做下去。一开始我们就直接开始对那 \(13\) 个跑自动机,然后跑完作为起点。然后我们枚举现在要用多少张,这个东西相当于从一些不同的牌中选一些张,所以要乘一个组合数。

然后我们外层循环枚举 \(i\) ,然后遍历 \(j\) , \(k\) ,然后往下转移,这个是对的。然后 \(g\) 也好统计

所以现在只剩下了一个难点,如何建立自动机?同样的,一开始我们什么牌都没有,然后我们枚举牌,然后往后走,然后就好了?

题解里面有一个把胡的矩阵变成同一个的这一个操作,比较神秘。我们想要的是一旦它胡了,我们就会一直胡下去,这也就是我们七对子赋值 \(-1\) 的原因。

P8497

题目套题目套题目,所以我们从最里面的开始考虑

首先我们有游戏一,也就是一个清空堆的操作和一个区间减的操作

我们发现如果所有堆都 \(>=2\) ,那么这个显然很简单。

否则我们要对于那些 \(==1\) 的堆进行区间减操作。

区间减,我们还是差分数组。那么操作一就是给 \(i\) 减去 \(x\) ( \(x>=2\) ), \(i+1\) 加上 \(x\) ( \(x>=2\) ),然后一个对于 \(i\) 减去 \(1\) ,对于 \(j\) 加上 \(1\) ,然后( \(j-i>=2\) )。要求最后所有都变成 \(0\)

发现这还是不太好,我们要求每个数都 \(>=2\) 即可,我们希望的是最终的状态能每个数要么 \(=0\) ,要么 \(>=2\)

所以我们想求对于一句游戏使它合法的最小要放的石子数。随着石子数的增加,我们游戏的胜利状态肯定是单调改变的。设 \(dp[i]\) 为到 \(i\) 的最小要放的石子数,但是这真的很困难。设 \(dp[i][j]\) 为。

题解纯神啊

先考虑去掉这个序列长度 \(>=3\) 的限制
所以我们前面伸过来的长度最多只有 \(3\) 种。设 \(dp[u][i][j][k]\) 为做到 \(u\) 了,然后从 \(u-3\) 之前伸过来的有 \(i\) 个,从 \(u-2\) 伸过来的有 \(j\) 个,从 \(u-1\) 伸过来的有 \(k\) 个。

我们必须消耗 \(j+k\) 个,然后我们枚举我们和 \(u-3\) 前面匹配的有 \(a\) 个,然后这个位置预留 \(b\) 个作为新的开头。然后我们还可以抛弃一点 \(u-3\) 前面的东西。然后合法是剩下的 \(>=2\) 或者 \(=0\)

我们还是往前贡献,这样好写一些。

我们限制一下 \(i\) , \(j\) , \(k\) 这三维的大小。因为我们只要长度为 \(3\) , \(4\) , \(5\) 就足以满足所有情况,所以三维都只有 \(4\) 的大小。

题解提示,我们的第二维和第三维可以合并。我们设 \(dp[i][j][k]\)\(i\) 之前,钦定了 \(j\) 个贡献到了 \(i\) ,还有 \(k\) 个必须贡献给 \(i+1\) 是否可行。眨眼一看好像没有考虑完所有情况,但这个东西是可以转移的。额,感觉这种状态设计出来也是我 \(\text{dp}\) 的瓶颈吧。

考虑钦定贡献到 \(i+1\) 的数量,也就是说我们现在在考虑如何把 \(dp[i]\) 转移到 \(dp[i+1]\) ,设从 \(i\) 开头的操作有 \(p\) 个,那么 \(a[i]-p-j-k\) 要为 \(0\) 或者 \(>=2\) 。然后我们发现 \(k\) 个必须贡献给 \(i+1\) 的数现在变成了可以钦定的东西。所以我们给 \(a[i+1]\) 钦定的数的范围就是 \([k,k+j]\) ,然后 \(k\) 就是 \(p\) ,然后就好了。

看看上面 \(\text{dp}\) 的状态数大小, \(j<=6\)\(k<=3\) 。发现重要性质,如果我们有一个从 \(i-1\) 开头的 \(4\) , \(5\) ,这个东西等价于我们从 \(i\) 开头的 \(3\) , \(4\)

所以说,比如我们 \(i\) 开头钦定给了 \(i+1\) \(3\) , \(4\) , \(5\) ,那么我们的 \(4\) , \(5\) 显然可以变成在 \(i+1\) 贡献的 \(3\) , \(4\) ,然后 \(i+1\) 的东西 \(i+1\) 再考虑吧!所以我们一个位置其实最多填 \(3\) , \(4\) 。所以我们的 \(j<=3\)\(k<=2\) 也就不难理解了。也就是 \(i-1\) 会给一个 \(3\) , \(4\) , \(i-2\) 会给一个 \(4\)

还有高手? \(j<=2\) 啊,考虑 \(i-1\) 会给一个 \(3\) , \(4\) , \(i-2\) 给一个 \(4\) 的情况,这个应该还是可以化简的。不会不会,这真不会证明。

然后我们就可以开始 \(\text{dp}\) 啦!

\(10\) 分,赢麻了

是时候考虑 \(k\) 的限制了。随着 \(k\) 增大,胜利状态肯定是单调的,这个证起来很简单。

假!看看题解在干什么。

题解说的是因为我们是强制放入 \(k\) 个,我们希望的是至多放入,也就是去弱化这个条件。也就是说我们找到以下情况,使得 \(k\) 比较小的时候可行,大的时候不可行。

\(k=0\) ,显然不影响

\(k=1\) ,我们需要找到 \(k=0\) 有解但是 \(k=1\) 无解的情况

这两个是一个交的关系,我们分开来搞。

对于 \(k=0\) ,其有解当且仅当就是一点都不邪道的 \(+\) 全为 \(0\) 的情况

全为 \(0\) ,显然 \(k=1\) 是无解的,否则怎么会无解呢?无解当且仅当没有 \(1\) 操作吧。也就是说一个序列无论怎么样都不能进行 \(1\) 操作,只能通过 \(2\) 操作来消完,这样就可能会导致无解。道理也很简单,我们在操作 \(1\) 那个位置再加一个 \(1\) 就行了。

如果我们加的位置是一个 \(2\) 操作的左右端点旁边也可以。兑。那么什么时候 \(2\) 操作会没有端点呢?如果 \(n=3\) , \(4\) , \(5\) ,所有 \(ai=1\) 吧。但是我们也可以叠在一个操作二的端点上,所以只有 \(n=3\) , \(ai=1\) 才是对的。

对于 \(k=2\) 的话

如果 \(k=0\) 有解,当前无解。我们可以给一个位置放 \(2\) 个石子。

如果 \(k=1\) 有解,当前无解,这其实就是加一个石子,变成有解的情况。显然不可能是 \(ai\)\(=0\) 的情况,只可能是 \(1\) \(1\) \(1\) 的情况,那么我们还原一下,就变成了 \(1\) \(1\) \(0\) ,那么我们对两个 \(1\)\(+1\) ,然后这个就变成合法的了。

对于 \(k>2\) 的话,如果 \(k-2n\) 有解,那么 \(k\) 一定有解。如果 \(k-2n+1\) 有解的话,我们还是讨论 \(n=3\) 的情况,其它的显然都是简单的,那么根据 \(k=2\) 时, \(n=3\) 的证明把这个东西搞了就行了。

所以我们就证完了!

所以我们设 \(g[i][j][k]\) 为使得 \(f[i][j][k]=1\) 所需要的最少石子数。这个东西转移?我们转移过来的时候,如果是合法的转移过来的时候,我们就可以直接转移,如果是不合法的转移,那么我们按需求给它加上石子。

现在我们对 \(k>0\) 判定已经想完了,现在是对 \(k=0\) 计数。

考虑 \(\text{dp}\)\(\text{dp}\)

仿照麻将的套路,我们需要建出一个自动机。然后设 \(dp[i][j]\) 为当前做到 \(i\) 位,然后做到自动机上状态 \(j\) 的方案数。这个自动机长什么样子呢?我们每加入一堆石子的时候,就相当于我们的转移了。

所以!我们在建自动机的时候,枚举这堆的石子数 \([0,100]\) ,然后搞出目标的矩阵,然后大力自动机即可。中间好像有一点点东西。

我们发现 \(ai>8\) 的情况都是本质相同的。你更厉害一点,我们直接取到 \(6\) 就可以了。你最厉害可以直接取到 \(4\) ,不知道为啥,真的,太逆天了

所以我们的矩阵差不多就是这个样子的前面是 \(\text{dp}\) 方程,后面是答案。你更厉害一点,我们这个东西可以直接变成一个 \(ull\) 存的下的东西。确实确实。但还是 \(vector\) 方便一点是吧,毕竟可能已经够难写了。

现在继续考虑 \(ai\) 的影响。因为 \(a[i]\) 在大的时候我们直接把大的压到 \(6\) 去做,然后乘上种数就行了。开写吧。有点畏惧了

注意到我们这里的 \(\text{dp}\) 状态里面全都是 \(101\) 也可能是全都不合法的情况,所以我们这个状态不能对所有合法的状态全部压到一起

posted @ 2025-11-11 22:18  wuhupai  阅读(5)  评论(0)    收藏  举报