[专题总结] DP记录 I
- Most Important : 当设计出来一个算法时,手动模拟看一种方案会被计算几遍是防止重复的最好办法。
Dp-最优化问题
省选模拟12A
考场上只会写一个超级 naive 的 dp, 也是无脑的常规思路。
设 \(dp_{i,j}\) 代表这次在 \(i\) 选择,上次在 \(j\) 选择的最大划分价值。
转移去枚举一个 \(k\) , \(dp_{i,j} = Max_{k=1}^{j-1}(dp_{j,k}+(max(j+1,i)-max(k+1,j)^2+C))\)
去斜率优化这个柿子,复杂度 \(O(n^2)\) 。
这个东西是没有优化前途了,因为状态数就是 \(n^2\), 还上不了线段树,只能考虑改变状态定义。
当发现状态数过多的时候,大概率就是你维护了很多无用的状态,就是把没有用或者可以代替的信息加到了状态里面
考虑简化状态,我们发现,转移只需要用到最大值,而并不管你每一段左右边界到底是在哪里,这步就是这道题的突破口了。
设计状态 \(f_i\) 代表上一段的最大值在 \(i\) 的最大价值,每次转移考虑第 \(i\) 个点是当前段的最大值,可以从哪些段转移。
发现 \(i\) 可以从 \(j\) 转移过来,当且仅当 \(i,j\) 中间没有比 \(i,j\) 大的数,这个和 影魔 的一部分是一样的,可以对每个数求一个 \(l_i\) ,代表 \(i\) 能转移的左边界,求的话可以二分+ST表 一个 \(log\)。
转移柿子也很简单 \(f_i = (f_j+(A_i-A_j)^2+C)\) ,显然可以斜率优化,可以选择线段树维护凸包两个 \(log\) 实现,但是跑 \(1e6\) 还是很吃力。
这个时候状态已经合法,就是转移复杂度比较高,一个常用的技巧是 最优化问题的等价手段,也就是最优化问题中,我们只需要保证最优解被转移,不关系其他解是否合法,是否正常转移
所以我们考虑,线段树去维护这个凸包,真的有用吗?
就算在当前点转移了一次不合法,那么他肯定不如合法转移两次更优。
因为 \(a<b, c<b\) 所以 \(|c-a|<|c-b|\) 或者 \(|c-a|<|a-b|\) ,这样不会导致更优的决策,所以直接用动态凸包/李超树维护上面的转移就可以了,不需要另外限制一维偏序。
省选模拟14 A
考场设计了一个离谱的区间dp,枚举第一次删掉了什么,根本没有正确性。
上述做法错误的本质是没有变成一个子问题,dp转移的常用套路就是枚举最后一次操作。
先假设 \(dp_{l,r}\) 代表把 \([l,r]\) 删掉的最大价值,最后可以线性 dp 拼接一下,这是区间 dp 处理不一定全部选择的经典套路。
具体的拼接方法是设 \(f_i\) 代表前 \(i\) 位的最大价值,枚举上一次在哪里选在加上这一段的价值就可以了。
转移考虑 \(a_l, a_r\) 是否在同一次操作中删掉,如果没有可以独立成两个子问题,枚举划分点 转移就可以了。
关键在于 \(l,r\) 在同一次被删掉,怎么统计,这个时候,不难发现我们删掉的就是 \([l::r]\) 的一个子序列,并且这个子序列是一个单峰。
可以选择去 \(dp\) 这个子序列, \(dp_{i,j,k}\) 代表 \([i::j]\) 选了 \(k\) 个数的最大价值,这个 \(dp\) 转移枚举上一次选的是什么点,复杂度是 \(O(n^4)\) 的。
发现一个性质,记录这个长度和转移并没有关系,只是和统计答案有关系,他是我们优化的切入点,本文多次提到。
最后我们是一个单峰,枚举一下最大值在哪里,那么 \(l->mx,mx->r\) 的长度可以 \(O(1)\) 统计,优化掉了一维状态,需要分成向上和向下分别 dp.
NOI模拟 6.23 A
简单观察一下性质,发现答案肯定是以管辖点为中心,划分成若干个不相交的联通块。
考虑 Dp 解决这个问题, 不妨先去尝试 \(Dp_{i,j,k}\) 代表 \(i\) 子树内, 有 \(j\) 个点想要向外匹配,离 \(i\) 最近的点距离 \(i\) 为 \(k\), 最小的花费。
因为 \(i\) 子树内肯定有若干个点要出去匹配,所以这个状态包含了所有。
考虑去优化他,利用我们 "肯定是划分成若干个联通块" 的性质,我们可以知道任意两条路径不会背道而驰。
也就是 \(i\) 子树内想要有点出去匹配,就不用记录 \(i\) 子树内最近的管辖点距离,反之同理,所以第三位可以变成 0/1。
然后合并的时候转移即可,都快 NOI 了,这么签到的题还是不能秒,不免有些寒心。
说实话这题我没想多久 Dp,最优化问题还是不能忘了 Dp, 果断观察性质,答案设计状态,是 Dp 的第一步。
这题还有一种做法,我们观察到他划分成若干个联通块,一个套路就是在每个联通块深度最小的点去统计贡献。
\(Dp_{i,j}\) 代表 \(i\) 的管辖点是 \(j\), 点 \(i\) 的子树找到自己的匹配,最小的代价。
从一个儿子转移分两种,要不他和我同色,直接是 \(Dp_{y,j}\) ,要么不同色,那么他就是他联通块深度最低点,统计贡献 \(\min (Dp_{y,j}) + C\)。
最后答案要加 \(C\) , 因为是覆盖 \(1\) 的颜色贡献还没有计算。
这个方法转移方便,主要原因是利用了只关心最优状态可以被转移的性质。
这也给了我们一个启发,最优化问题,设计Dp,我们只需要设计一个保证能转移最优解的Dp即可,对着最后的答案的性质设计Dp。
同时,这也是一个在转移实现复杂,或者转移复杂度高的时候的一种思考方向。
CF1476F Lanterns
Dp-计数
NOI模拟11.5 C
首先思考一下,发现题目的难点在于计算合并两个子树的时候,子树之间产生的贡献。
考虑用线性性把答案拆开,在 LCA 处统计 \((x,y)\) 的贡献。
设 \(x,y\) 在 LCA 的儿子是 \(nx,ny\) 。
这步期望的线性性是最关键的一步,考虑我们如果暴力就是枚举 \((nx,ny)\) 的每一种拓扑序对和合并方式,复杂度开销非常之大。
这个时候把所有方案列出来,换一个方向去考虑(是真的方向,转矩形的套路),也许统计的地方会少。
那么,考虑 \((x,y)\) 贡献的方式,假设 \(x>y\),那么 \(x\) 必须要在 \(y\) 前面。
暴力的去枚举 \(x,y\) 在 \(nx,ny\) 的位置 \(rk_x, rk_y\),以及合并之后的位置 \(nrk_x, nrk_y\)。
配以 \(dp_{x,y,k}\) 代表 $x $ 在 \(y\) 子树内第 \(k\) 个的方案数,可以计算贡献。
就是三个部分分别组合数, 复杂度大概 \(O(n^6)?\) ,肯定要去优化的。
首先可以优化的是枚举 \(y\) 在新序列内的位置,这明显赘余,因为我们只要它在 \(x\) 后面。
我们只需要枚举一个 \(x\) 的位置,就可以计算答案。
也就是让 \(y\) 跟着后面的东西一块去混着组合, 我猜测组合技巧高深可以把上面那个东西化成这个,不过思考组合意义去优化确实很重要。
然后再来写套路的优化:发现 \(rk_y\) 是独立的,所以分开枚举,先计算出来 \(x\) 的那坨玩意,整一个 \(y\) 的后缀和可以优化。
发现计算 \(y\) 的时候顺便可以把 \(ny\) 子树内所有点贡献计算了,那么我枚举一个 \(x\), 最多让所有点对贡献一次,复杂度 \(O(n^3)\) 。
再考虑怎么去求这个 \(DP\) 数组,暴力合并子树,你需要给每个状态 \(dp_{x,y,k}\) 枚举一下另一个子树内有多少个点在他前面。
所以合并一个大小为 \(x\) 的子树和 一个大小为 \(y\) 的子树的复杂度是 \(x^2*y+y^2*x\) 的。
总复杂度 \(O(n^3)\) , 证明的话考虑合并两个子树就是在这两个子树内选 3 个点, 组合意义证明复杂度是重要手段。
本题有 \(O(n^2)\) NB 做法,参见link
Uoj Round 20 跳蚤电话
联赛模拟39 B
- 竞赛图缩点之后一定是一条链。
考虑每个诱导图不是强连通的点集 \(S\) ,那么肯定存在一个集合 \(T\) , 使得 \(T -> (S/T)\) 的边都是 \(T\) 出发的。
那么类似筛法,在每个集合把他所有的出边的交的子集并上他刷新成不合法。
处理一个点集所有出边的可以用 \(x=(x-lowbit(x))|(lowbit(x))\) 的套路去求,利用的是状压枚举肯定是把一个点子集都枚举完之后再枚举的他 。
复杂度 \(2^n\) 因为每个不合法集合只会在链的最后一条边被刷新。
Dp-边DP边容斥
这是一类很特殊的 dp。
通常,我们设计出来的状态不能直接从原来的状态转移,但是,原来的状态可以通过变换变换成现在状态的补集。
每次用总的方案数减去当前状态的补集方案数来转移。
他就是普通容斥做不了的题,只能去想dp,但是这类题设计不出什么别的状态,只能设计这个状态。
你又无法直接从原来的状态去转移,所以只能每次用全集减去不合法的集合。
重要的trick是在统计不合法的时候,要确定一个不合法集合的划分方式,保证不重。
比如图计数,就是找和1一个联通快的点。
还有2.12省选模拟,既然存在border,就枚举最小的border的长度,剩下的任意。
这样的不合法集合划分才能保证每个不合法集合被统计一次。
同:一堆图计数,AGC 056B,2.12省选C,卡农。
还是和容斥一样的钦定思想,本质是去简化限制,方便转移,不过用dp实现。
Trick1-一类需要满足值单调递增的 dp
可以用来对付无标号避免重复,有的时候就是题目的要求。
一般的转移都是考虑 给之前所有的都加上1,和加入一个/一些 1,可以保证是单调的。
应用有 整数划分数 \(P_{i,j}=P_{i,j-i}+P_{i-1,j-1}\) , Acwing277 ,知道这个套路就比较简单了。
Trick2-一类成环的 dp
处理的环基本套路1 : 断环为链,复制一倍加在后面
一般的环都可以用这个转化,转化完之后可以区间dp
处理的环基本套路2 : 给定一个点,第一次强制选,第二次强制不选,这样就能统计所有情况。
Acwing288
第一次强制在1点不能睡,第二次强制在一点睡,可以发现统计了所有情况。
环上不相邻组合
有 \(n\) 个点形成的一个环,从中选出 \(m\) 个,这 \(m\) 个不相邻的方案数。
先来看链上不相邻的方案数,把需要的 \(m\) 个先拿出来,再在剩下的 \(n-m+1\) 个空中选 \(m\) 个放进去,答案是 \(\binom{n-m+1}{m}\)。
再来看环上,第一次我们强制选择 \(1\) ,剩下的问题就是在 \([3,n-1]\) 种选择 \(m-1\) 个不相邻的,方案数 \(\binom{n-3-(m-1)+1}{m-1}=\binom{n-m-1}{m-1}\)
第二次强制不选择 \(1\),方案就是在 \([2,n]\) 中选择 \(m\) 个不相邻的,方案数 \(\binom{n-1-m+1}{m}=\binom{n-m}{m}\)
两个加起来就行了,可以发现一种方案只会被统计一遍。
Trick3-一类排列的 dp
- 记录相对位置!!
毛一,二三? 刺头,https://atcoder.jp/contests/jag2018summer-day2/tasks/jag2018summer_day2_k, NOI冒泡排序。
Trick4-一类可能需要枚举集合划分的优化
\(3^n\) 枚举子集优化,每次加入一个集合。
抑郁刀法,最小斯坦纳树,Abc234 Ex。
Trick5-一类dp的转移
考虑一次操作能使什么状态转移过来,这是入手点,就是把能转移过来的状态集合划分成不相交的几个部分。
例:求一个序列所有子序列的本质不同子序列的个数和。
他是要求所有子序列的子序列自动机中路径的条数,考虑把答案划分成最终谁是终点。
设计 \(f_i\) 代表以 \(i\) 结尾的子序列中,所有以 \(i\) 结尾的路径条数。
那么,最终答案内任何一条以 \(i\) 结尾的路径,都需要在这被统计。
考虑 \(f_i\) 中每条路径,都会被统计 \(2^{n-i}\) 次,因为后面的点有选不选两种转态,但是都会统计到 \(f_i\)
所以答案是 \(\sum f_i2^{n-i}\)
考虑转移到 \(f_i\),划分能转移过来的集合,这个路径现在以 \(i\) 结尾,这个路径上次以什么结尾,是一个不重不漏的划分。
要想在自动机上从 \(i \rightarrow j\) ,必须保证 \(i\) 是 \(j\) 前面第一个 \(s_i\) ,所以 \([i+1,j-1]\) 种等于 \(s_i\) 的不能选择,其余任意选。
那么,上一个点是任意一个的都转移了,自然就不重不漏了,\(f_{i}=\sum f_j 2^{cnt_ij}\)。
类似:错排,斯特林数递推式,模拟74游戏,都非常经典。
Trick6-一类类似插头 DP 转移对转移的优化
CF1519F, 一道模拟赛的数位Dp。
Trick7-一类多点初始化的 DP
https://www.cnblogs.com/C202044zxy/p/16463153.html
ABC259 Ex
https://tg.hszxoj.com/contest/73/problem/3
https://www.cnblogs.com/7KByte/p/15359993.html
https://www.luogu.com.cn/problem/CF1142D
Trick8-一类预设状态型dp
noip模拟糖果, seq growing hard, AGC002F,dp搬运工。
Uoj #390. 【UNR #3】百鸽笼
只是一个部分分,但是这个做法本身非常巧妙并且很套路,简单记录一下。
如果我们要保证复杂度,那么只能记录那些列还空着,一次加满一列, 剩下的只能在状态里面存着。
设计 \(dp_{i,j}\) 代表加入了 \(i\) 个管理员, \(j\) 这个状态的列还空着的概率。
转移,要不就是先加入一个管理员,但是不确定加到哪里。
要不就是把一列去填满,在新加入一个填满的时候,我们只需要去前面未确定的管理员里面选出来 \(a_i-1\) 个管理员即可。
但是发现还有一个事情,就是选择他们的概率,我们当然可以再开一维状态记录上一次是啥时候选择的,只不过会提高复杂度。
只要不是统计答案需要的状态都可以考虑转移的时候优化掉
我们在加入 \(i\) 的时候把所有选择的管理员的概率乘上 \(\frac{sz-1}{sz}\) , 最后,每个管理员的概率都是正确的,利用了 \(\frac{1}{x}=\sum_{i=2}^x \frac{x-1}{x}\) 。
Trick9-01原则在计数的应用
01原则是把序列变成 01 从而简化问题,在数据结构方面常用于排序,比较等问题。
在计数领域也有他的应用,还是一般在比较等方面,额外的应用是配合线性性拆开从而把 \(\leq x\) 的变成 \(0\), \(>x\) 变成 \(1\) 。
NOI模拟7.23 B
那么首先考虑暴力, \(f_{S,k}\) 代表进行了 \(k\) 轮排序,状态是 \(S\) 的概率,转移 \(2^n\) 枚举,当然可以使用 Trick6 去优化。
现在考虑线性性拆开统计每个地方 \(>x\) 的概率,我们把所有 \(\leq x\) 的地方变成 \(0\), \(>x\) 的变成 \(1\) ,最后给每个状态里面所有的 \(1\) 的答案加上这个状态的概率,那么转移还是Trick 6去优化,如果我们枚举每个 \(x\) 都做一遍,复杂度是 \(n(nm2^n)\) ,发现他们的转移都是相同的,所以直接 Trick 7优化,复杂度 \(nm2^n\) 。