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数组。

[ARC073F] Many Moves

一般是 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.

P5298 [PKUWC2018] Minimax

首先是朴素DP。

定义\(f_{u,i}\)表示结点\(u\)最后权值为\(i\)的概率(对应题中的\(D_i\))。观察到题目给的是二叉树,然后开始想朴素转移:

\[\begin{aligned} f_{u,i}&=(1-p_u)(f_{ls,i}\sum\limits_{j>i} f_{rs,j}+f_{rs,i}\sum\limits_{j>i} f_{ls,j})+p_u(f_{ls,i}\sum\limits_{j<i} f_{rs,j}+f_{rs,i}\sum\limits_{j<i} f_{ls,j})\\ &=f_{ls,i}\Big[(1-p_u)\sum\limits_{j>i}f_{rs,j}+p_u\sum\limits_{j<i}f_{rs,j}\Big]+f_{rs,i}\Big[(1-p_u)\sum\limits_{j>i}f_{ls,j}+p_u\sum\limits_{j<i}f_{ls,j}\Big] \end{aligned} \]

方程里的前缀后缀和直接优化就到\(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\)为空时同理。

由于题目中给了权值互不相同,所以两棵线段树合并时值域不会重叠,于是肯定会出现一棵为空而另一棵不会空的情况。所以就做完了。

决策单调性优化

四边形不等式

就是利用四边形不等式判断决策是否有单调性。

\[\text{对于}a\le b\le c\le d,\text{如果满足}w(a,c)+w(b,d)\le w(a,d)+w(b,c),\text{则称}w\text{满足四边形不等式。} \]

四边形不等式给出的是决策单调性的充分不必要条件,也就是说不满足四边形不等式时也可能有决策单调性。

证明不会。

单调性猜测一下就好,考场上证不了一点,可以打表找找规律看是否单调。

有了决策单调性就好办了,主要有两种方式来优化DP。

单调队列+二分

我们把决策塞到单调队列里面(其实很自然,决策有单调性嘛),然后维护一下单调性即可。

让我们更具体一点。

队列中保存决策\(d(l,r,p)\),表示区间\([l,r]\)的当前最优决策点为\(p\)

  1. 首先队列中塞入\((1,n,0)\)(或者其他初始条件),准备开始DP。

  2. 现在我们要计算\(dp_i\),先从队首将\(r<i\)的决策出队(因为已经没用了),然后用队首计算\(dp_i\)

  3. 然后考虑插入\(i\)这个决策。不断取出队尾\((l,r,p)\),若用\(i\)更新\(l\)更优,那么队尾的这个决策就是没用的,出队。

  4. 最后对于队尾\((l,r,p)\),用\(p\)更新\(l\)更优,那么\(i\)这个决策可能没用,也可能在\(l\)\(r\)之间某处开始比\(p\)更优。于是二分,注意边界要开大来判断无解(就是\(i\)没用的情况)。

  5. \(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)\)

注意事项

  1. 带有上取整,下取整,绝对值的大概率不满足决策单调性,要转化(绝对值就拆掉,取整就先用long double算,最后再取整)。

  2. 可以猜单调性但别乱猜,最好能打表,时间充裕再证。

凸优化

或许还是某种决策单调性的表现(?

有凸性那决策集合就很好维护了。

斜率优化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的通项的式子,然后算这个式子就好。

或者瞪出来是个关于某一维的多项式,算出前几项然后拉插。

posted @ 2024-12-24 11:10  RandomShuffle  阅读(100)  评论(0)    收藏  举报