DP优化
朴素DP都写不出来怎么办捏qwq
纵观各种优化方式,可以发现DP的优化在于两个方面:转移和状态数。
转移的优化很多,大多数的优化都关注于转移。而状态数的优化大多是针对状态的设计。
一类特殊的DP:整体DP,试图兼顾这两个方面,利用DS囊括各种DP的状态时,同样利用DS的结构将具有相同转移的状态一起转移。
矩阵快速幂优化
将转移写成\((\max,+)\)矩阵乘法的形式,然后上矩阵乘法,可将\(O(n)\)的转移优化到\(O(\log n)\)。
矩阵的构造函数一定要写啊啊啊啊啊。小心出现神秘错误。
前缀和优化
一般的DP加上前缀和可以优化形如\(f_i=\sum\limits_{j=l}^r g(j)+C\)的式子。
对于一段连续区间求和,且\(g(j)\)是一坨只关于\(j\)的式子(如\(f_j,f_j+w_j\))加上常数(关于\(i\)的式子或者真的常数)就可以使用。
特别的,如果区间的一端是固定的,只增不减,用变量存一下就好了。
单调队列优化
优化形如\(f_i=\min/\max_{j=l}^r g(j)+C\)的式子,其中要保证随着\(i\)增大,\(l,r\)单调不减。
\(g(j)\)是一坨只与\(j\)相关的式子,\(C\)同上文。
本质是及时排除不可能再有用的决策。
单调栈与单调队列都是类似的。
特别的,如果区间一端固定,另一端只增不减,用变量存一下就好了,不必用单调队列。
数据结构优化
特征:
一个状态可以从某个区间中的每个状态转移过来,且随着状态改变,对应区间没有单调性。
Sol:
用数据结构维护一下决策即可。
其实单调队列优化也算是特殊的DS优化,但是对应区间有单调性的性质很好,所以跑得更快。
线段树优化DP
其实感觉细分下来还是有几种啊。记录一下。
线段树优化找决策点
\(f_i=\max\limits_j f_j\),合法的转移是一个区间,线段树维护区间 max 之类的。
线段树维护 DP 数组
特点是发现一次转移对应在DP数组上的区间修改和单点修改。于是用线段树维护DP数组。
一般是 2D 的 DP。并且其中一个维度 \(i\) 只和 \(i-1\) 相关。
线段树维护决策
类似 \(f_i=\max\limits_j g_j(i)\)。
用线段树维护 \(g_j(i)\),可能 \(i\) 变化时前面很多 \(g\) 都要变化,需要用线段树支持区间修改,区间查询。
平衡树优化DP
其实很多时候用的是unordered_map或者map,把DP数组的一维压到这里面,并且可以快速查找可以转移的位置。
手写平衡树时,支持对 DP 数组的插入删除区间操作。
李超线段树优化DP
形如\(f_i=\min_j\{kx+b\}+C\)一类的柿子,其中\(x\)与\(i\)相关,\(k\)和\(b\)与\(j\)相关。这样可以视作插入了一堆线段或者直线(这个区别在于一个位置能对哪些位置做出贡献),查询一个位置处一次函数最值。可以简单用李超线段树维护。
如果维护一堆直线就是单\(\log\),维护线段是双\(\log\)。
维护线段的情况中如果线段有凹凸性就可以斜率优化。
整体DP
其实和数据结构优化DP有类似之处。
但是整体DP直接将DP中的一维压入数据结构之中。
通过数据结构上的单点修改/区间修改来快速维护此时此刻(枚举到的其他维度)的各个位置的DP值。
感觉像是将转移方程中的运算直接搞成修改。
可以发现整体DP是对\(O(1)\)转移的DP的再优化(因为状态数太多了),用数据结构就可以在状态数很多的情况下也能够实现快速转移(比如线段树中通过标记把一堆状态一起改)。
做的时候还是要先写朴素DP,然后通过一些手段优化到\(O(1)\)转移,然后上数据结构对状态数很多的情况进行优化。
线段树合并
维护树上的整体DP很方便(因为合并)。
普通的序列上的线段树不太方便维护树上的信息,于是改造成线段树合并。
维护时同样支持单点修改/区间修改 etc.
首先是朴素DP。
定义\(f_{u,i}\)表示结点\(u\)最后权值为\(i\)的概率(对应题中的\(D_i\))。观察到题目给的是二叉树,然后开始想朴素转移:
方程里的前缀后缀和直接优化就到\(O(1)\)转移了。
但是由于值域很大,状态数超级多,\(O(1)\)转移也无法接受。于是上线段树合并,同时维护前缀和与后缀和以及乘法标记。
具体而言,点\(u\)上的线段树维护着\(f_{u,1\dots V}\)。现在考虑如何通过线段树合并实现上面的转移。
首先,当点\(u\)没有儿子,是叶子时,将其权值插入到它的线段树中,概率设为\(1\)。当点\(u\)只有一个儿子时,直接继承儿子的线段树树根。当点\(u\)有两个儿子时,合并两个儿子,将合并后的树根给\(u\)。
合并中,改造一下原来的merge,记录当前合并的线段树节点\(lsu,rsu\),当前合并的区间\([l,r]\),区间\([1,l-1]\)中\(ls\)和\(rs\)的前缀和,\([r+1,V]\)中\(ls\)和\(rs\)的后缀和,以及\(p_u\)。
当\(lsu\)为空时,方程中左边那一坨都为\(0\),右边\(f_{rs,i}\)乘上的一坨为定值,所以直接区间乘打上标记。当\(rsu\)为空时同理。
由于题目中给了权值互不相同,所以两棵线段树合并时值域不会重叠,于是肯定会出现一棵为空而另一棵不会空的情况。所以就做完了。
决策单调性优化
四边形不等式
就是利用四边形不等式判断决策是否有单调性。
四边形不等式给出的是决策单调性的充分不必要条件,也就是说不满足四边形不等式时也可能有决策单调性。
证明不会。
单调性猜测一下就好,考场上证不了一点,可以打表找找规律看是否单调。
有了决策单调性就好办了,主要有两种方式来优化DP。
单调队列+二分
我们把决策塞到单调队列里面(其实很自然,决策有单调性嘛),然后维护一下单调性即可。
让我们更具体一点。
队列中保存决策\(d(l,r,p)\),表示区间\([l,r]\)的当前最优决策点为\(p\)。
-
首先队列中塞入\((1,n,0)\)(或者其他初始条件),准备开始DP。
-
现在我们要计算\(dp_i\),先从队首将\(r<i\)的决策出队(因为已经没用了),然后用队首计算\(dp_i\)。
-
然后考虑插入\(i\)这个决策。不断取出队尾\((l,r,p)\),若用\(i\)更新\(l\)更优,那么队尾的这个决策就是没用的,出队。
-
最后对于队尾\((l,r,p)\),用\(p\)更新\(l\)更优,那么\(i\)这个决策可能没用,也可能在\(l\)与\(r\)之间某处开始比\(p\)更优。于是二分,注意边界要开大来判断无解(就是\(i\)没用的情况)。
-
若\(i\)没用,就跳过了去算下一个。否则要插入\(i\)这个决策。设二分出\(i\)比\(p\)更优的第一个位置为\(u\),修改\((l,r,p)\)为\((l,u-1,p)\),插入决策\((u,n,i)\)。
分治
是离线版本的决策单调性优化,即当前的DP数组从另一个现在完全已知的数组中转移过来。也就是说从\(f_j\)转移到\(f_i\)的转移方式不可以用分治。
但是分治真的很好写。
设当前要确定决策点的区间为\([l,r]\),其对应的决策点所在区间为\([dl,dr]\)。
那么我们可以先算\(mid=\frac{l+r}{2}\),暴力扫一遍\([dl,dr]\)确定\(mid\)的决策点\(dm\),然后向下分治:
\([l,mid-1]\)对应的决策点区间为\([dl,dm]\),\([mid+1,r]\)对应的决策点区间为\([dm,dr]\)。(含有\(dm\)是因为不一定严格单调)
然后就做完了。
指针移动的Trick
有些\(w(l,r)\)不好\(O(1)\)求,只能\(O(n)\)用指针扫。
但是每一层都暴力扫会假,这是因为暴力扫是从决策区间的一个端点扫到序列区间的\(mid\)。这玩意一点保证都没有,随便卡卡都会寄。而莫队写法是将两个端点分开了,在各自的分治树上是有保证的。
于是可以在分治外维护指针,每一层要算贡献时就像莫队一样移动指针即可。注意这样每层的指针移动量是\(O(n)\)的,所以总的还是\(O(n\log n)\)。
注意事项
-
带有上取整,下取整,绝对值的大概率不满足决策单调性,要转化(绝对值就拆掉,取整就先用
long double算,最后再取整)。 -
可以猜单调性但别乱猜,最好能打表,时间充裕再证。
凸优化
或许还是某种决策单调性的表现(?
有凸性那决策集合就很好维护了。
斜率优化DP
形如\(f_i=\min\{f_j+C(j)-K(i)A(j)\}+B(i)\)。
其中\(K(i),C(j),B(i),A(j)\)是关于\(i,j\)的一次项。
变形为\(f_i-B(i)=\min\{f_j+C(j)-K(i)A(j)\}\)。
令:\(y=f_j+C(j),x=A(j),k=K(i,j),b=f_i-B(i)\),那么原式就是\(b=y-kx\)。然后对于一个\(i\),就是用固定的\(k\)移动一条直线去截平面上已有的点,使得截距最小。
注意到有用的点都是凸壳上的点(上凸或者下凸)。
单调队列维护凸包
和普通的单调队列一样。以下凸为例,弹出队尾当且仅当\(k_{q_t,q_{t-1}}>k_{q_t,x}\)。
注意:如果要二分,上面的\(>\)应为\(\ge\),是为保证斜率的严格单调。
横坐标单调,斜率单调
那就是板子啊,随便单调队列就行。
注意横坐标不严格单调时可能相等的情况,算斜率的时候判一下。
横坐标单调,斜率不单调
还是单调队列维护点,然后二分查询。
横坐标不单调,斜率单调
没做过,或许继续单调队列,然后在队列中二分插入点。
横坐标不单调,斜率不单调
上李超线段树/CDQ分治/平衡树。
wqs二分
看到恰好\(k\)个,至多\(k\)个之类的限制,并且设\(f(x)\)表示恰好选了\(x\)个时的答案,若\(f(x)\)有凸性,那就可以wqs二分去掉\(k\)的限制,变成没有限制的DP。但是这个DP除了返回\(f(x)\),还要返回\(k\)这一维的数量。
凸性是懒得证的,看到恰好\(k\)个又不知道什么正确性有保证的其他做法时,直接wqs二分吧,公式法做题就是快。
从带权二分的角度,这是在二分一个偏移量\(\delta\)。给每个物品加上一个偏移量,想法就是如果物品多了,那就操纵偏移量来少选它,反之亦然。注意这个偏移量要打在物品上,并且不要让它干扰内层DP。类似加入一个物品然后又删去一个物品的做法是不可取的。需要明确什么是当前题目的“物品”。
从二分凸壳斜率的角度,二分一个偏移量其实是二分斜率。我们在\((x,f(x))\)处作凸壳的一条切线\(y=kx+b\),在这个点上就是\(f(x)=kx+b\),于是\(b=f(x)-kx\)。跑DP求出来的其实就是\(b\)的最值,于是确定了一条切线,DP还要返回\(x\),其实就是看是否切到\(k\)了。
二分内侧的chk就是跑DP。由于DP同时需要维护两个信息,一般可以写个结构体搞一下。难点还是在这里。注意定义状态时要回避偏移量的影响。
多点共线的情况
由于斜率都是整数,所以很可能有二分到一个斜率同时切到很多个点的情况。
发现除非使用实数二分,否则找不到只切到\((k,f(k))\)的斜率。
这个时候要钦定一些东西,我们二分到一个斜率后,认为它切到的点是所有共线的点中最左边或者最右边的点。
那这个时候我们得到的横坐标\(x\)可能并不是想要的\(k\)。我们需要对应地修改二分中的判定来更新答案。具体地,如果选最左边,那就\(x\le k\)时要更新答案。否则\(x\ge k\)时更新答案。
同时,在DP中,我们除了正常DP出最优的答案之外,还要尽量优化\(k\)这一维的数量。如果有两个决策可以得到一样优的答案,那么还要比较数量维的优劣。如果认为切到的点是最左边的,那就要最小化数量维,否则要最大化数量维。
Slope Trick
一般是优化二维的 DP。
用于 DP 的状态关于某一维是凸函数。可以归纳证明,或者根据一些数学知识,去看数学博客。
知道了是凸函数,并且转移是一种保持凸性的变换,那么可以转而维护这个凸函数。其中可以只保留知道答案所必要的信息,因为维护整个凸函数可能有些困难。
常见的手法有维护拐点(斜率变化有限)和维护斜率(斜率变化很大,但是段数有限)。
需要仔细地定义自己维护的东西是什么。
组合意义优化
推答案的式子,然后改一下形式成为另一个DP,或许能少一维状态,可降时间空间。一般是系数和状态中的一维有关系,然后用组合意义把系数放到DP过程中。
数学优化
均摊
比如什么\(\gcd\)之类的有均摊,然后预处理一下什么的就好了。
颓式子
大眼瞪出来DP的通项的式子,然后算这个式子就好。
或者瞪出来是个关于某一维的多项式,算出前几项然后拉插。

浙公网安备 33010602011771号