[笔记] DP的姿势

DP

作者水平十分有限,下文中难免错漏及不合理之处。恳请各位批评指正。

寻找拓扑序,设计状态,细心讨论转移,卡好边界

Floyd

\(f[i][j][k]\)表示从\(i\)\(j\),中间只经过编号小于等于k的点的最短路(其中i和j可以大于k)

转移考虑经过第\(k\)个点,即\(f[i][j][k]=min\left\{f[i][j][k-1],f[i][k][k-1]+f[k][j][k-1]\right\}\)

实际中第三维\(k\)可以省略

这样做的拓扑序是什么?其实由你规定,每个点都是等价的

一般情况循环1到\(n\),但实际上完全可以根据题目进行调整

+++

背包

背包问题具有拓扑性,即空间在不断减小,价值在不断增大,可以任选一个作为DP的阶段

01背包

\(f[i][j]\)代表前\(i\)件物品,占用空间小于等于\(j\),所获得的最大价值

考虑第\(i\)件物品,选择装入或者不装入

\(f[i][j]=max(f[i-1][j],f[i-1][j-V_i]+W_i)\)

我们发现第一维\(i\)只依赖上一层\(i-1\!\) ,因此可以巧妙设计循环顺序来避免错误转移

大胆设\(f[j]\)表示占用空间小于等于\(j\)时获得的最大价值

for(int i=1;i<=n;i++)
    for(int j=V;j>=v[i];j--)
        f[j]=max(f[j],f[j-v[i]]+w[i]);

方案数问题

  • 求方案数?\(max\)改累加

  • 若是题目要求“恰好”装满体积为\(m\)的背包?

    考虑背包的实际含义,我们只需要将除了f[0]以外的状态设为负无穷

  • 恰好装不下任何一件物品的方案数?

    粗略的想法是,物品按体积排序,枚举第\(i\)个物品,作为装不下物品中体积最小的,显然\(v\)小于\(v_i\)的都要装进去,就对剩余空间做一次背包计数即可

    复杂度?枚举\(i\)占用\(O(n)\),背包本身\(O(nm)\),总复杂度\(O(n^2m)\)

    这样是有优化的空间的,我们发现每个物品都被考虑了多次,倒着枚举\(i\)

    这样做的好处是,背包是一个后缀的背包,每次只需要加入一个物品,总复杂度在\(O(nm)\)

    边界就在统计答案时卡死即可,如设必装物品占用空间\(sum\),我们只需要统计\([m-sum,m-sum-V_i]\)范围内的ans

完全背包

\(f[i][j]\)表示前\(i\)个物品,占用空间小于等于\(j\),且可以多次选择的最大价值

同01背包的优化,只需正序循环,就可以使用之前的状态(也就是重复使用物品)

for(int i=1;i<=n;i++)
    for(int j=v[i];j<=V;j++)
        f[j]=max(f[j],f[j-v[i]]+w[i])

分组背包

\(t\)组物品,每组物品只能选取一个,问可以获得的最大价值

枚举物品时注意顺序,保证同组物品不重复选择(内层循环)

for(int i=1;i<=n;i++)
    for(int j=V;j;j--)
        for(int k=1;k<=num[i];k++)
            if(j>=v[i][k])f[j]=max(f[j],f[j-v[i][k]]+w[i][k]);

多重背包

每个物品可以使用\(t[i]\)

法1:

把每个物品复制成\(t[i]\)份,同01背包

复杂度\(O(nm\sum{t[i]})\)过高,不能接受

法2:

拆分物品的过程,就是给01背包提供选出\([0,t[i]]\)内任意数量的机会

我们无非是要拼出\(t[i]\)件物品,所以采用最高效的二进制拆分,把每个物品拆成\(log _2{t[i]}\)份,再使用01背包解决

复杂度\(O(nm \sum{\log_2{t[i]}})\),好像还行?

法3:

单调队列优化,见后文。复杂度\(O(nm)\)


*树形DP

树的直径

树上最远的两个点之间的简单路径,称为树的直径

可以通过两次DFS找最远点或者DP在\(O(n)\)的时间求出

  • 如何求树上所有点的最远点?

    每个点的最远点一定在直径的端点上,任取一条直径两个端点开始DFS即可

  • 基环树直径怎么求?

    分类讨论,直径要不出现在一棵子树中,要不出现在两棵子树以及一段环上

    先DP出子树直径,这是\(O(n)\)的,第一个情况可以通过取\(\max\)考虑,第二个情况怎么求?

    枚举环上两点是\(O(n^2)\)的,不能接受

    考虑破环为链,在一条链上DP,后文将用单调队列优化到\(O(n)\)

树的重心

删一个点后,树会分裂成若干个连通块

若分裂后连通块的最大值最小,则称这个点为树的重心(也就是各个连通块大小均衡)

可以通过DFS在\(O(n)\)的时间求出

用来点分治,保证分治的复杂度,相信大家都会正确的点分治

几个性质:

  1. 一个树最多有两个重心(两个重心时,这两个重心一定是一条边的两个端点)
  2. 删去重心后剩下的联通块最大的不超过\(\dfrac n2\)
  3. 重心到所有点的距离之和是树中所有点最小的

树的特性

二分图相关

不要忘记树是严格的二分图

但同时,树有更多优美的性质,可以更快地解决下列问题(贪心/DP)

不带权的问题

事实上,不带权的下列问题完全可以用贪心解决,也可以解决一个后,用二分图性质转化

最小点覆盖=最大匹配=N-最大独立集=N-最大匹配

最小链覆盖=最长反链=N-拆点图的最大匹配

接下来我们不考虑网络流做法/贪心解法,这就必须设计DP状态了

树的最大匹配

\(f[x][0]\)表示\(x\)为根的子树,\(x\)不和任何一个儿子连的最大匹配数

同理,\(f[x][1]\)表示以\(x\)为根的子树,\(x\)和某个儿子连边后的最大匹配数
转移:

\[\begin{align*} f[x][0]&=\sum\max\{f[v][0],f[v][1]\}\\ f[x][1]&=\sum\max\{f[v][0],f[v][1]\}+\min\{f[v][0]-f[v][1]\} \end{align*} \]

后面的\(min\)仅在\(x\)完全由\(f[v][1]\)转移来时计算,也就是说,至少有一个儿子未匹配

求方案数?

同时记录\(F,G,g\)数组,统计的就是最大匹配充要转移中等价的方案数

void dfs(int x,int pre){
	g[x][0]=1;
	for(int i=head[x];i;i=nex[i]){
		int v=to[i];if(v==pre)continue;
		dfs(v,x);
		f[x][0]+=F[v];
		g[x][0]*=G[v];
	}
	for(int i=head[x];i;i=nex[i]){
		int v=to[i];if(v==pre)continue;
		int t=f[x][0]-F[v]+f[v][0]+1;
		int p=g[x][0]/G[v]*g[v][0];
		if(f[x][1]<t){
			f[x][1]=t;g[x][1]=p;
		}else if(f[x][1]==t){
			g[x][1]+=p;	
		}
	}
	if(f[x][1]>f[x][0]){F[x]=f[x][1],G[x]=g[x][1];return;}
	if(f[x][1]<f[x][0]){F[x]=f[x][0],G[x]=g[x][0];return;}
	F[x]=f[x][0];G[x]=g[x][1]+g[x][0];
}
最大独立集

\(f[x][0/1]\)表示考虑以\(x\)为根的子树,且\(x\)不选/选的最大独立集

\(x\)不选,则与\(x\)直接相连的儿子\(v\)均可以选

\[f[x][0]=\sum\max\{f[v][0],f[v][1]\} \]

\(x\)选,则它的儿子均不可选

\[f[x][1]=\sum f[v][0] \]

最大权独立集:HDU 1520 Anniversary party 类比最大独立集

基环树最大独立集

先对每个子树求出\(f[i][0/1]\)表示以\(i\)为根的子树,选/不选\(i\)点的最大独立集,然后破环成链,做一次链上DP

最小点覆盖

\(f[x][0/1]\)表示考虑以\(x\)为根的子树,且\(x\)不选/选的最小点覆盖

\(x\)不选,为了覆盖\(x\)到儿子\(v\)的边,\(v\)必选,则有

\[f[x][0]=\sum f[v][1] \]

\(x\)选,则儿子\(v\)可选可不选,但是要增加1的代价

\[f[x][1]=1+\sum\min\{f[v][0],f[v][1]\} \]

最小支配集

一个点\(x\),有三种情况,分别考虑一下

  1. \(x\)在支配集里
  2. \(x\)不在支配集,且至少被一个儿子\(v\)支配了
  3. \(x\)不在支配集,且被父亲\(a\)支配了

情况一,既然\(x\)在支配集,那么\(v\)可以任意取,但是要增加1的代价

\[f[x][0]=1+\min\{f[v][0],f[v][1],f[v][2]\} \]

情况二,\(x\)至少被一个儿子支配了

  • \(x\)无儿子

\[f[x][1]=+\infty \]

  • \(x\)至少存在一个儿子\(v\),满足\(f[v][0]\leq f[v][1]\),那么

\[f[x][1]=\sum \min\{f[v][0],f[v][1]\} \]

  • \(x\)的所有儿子均不满足\(f[v][0]\leq f[v][1]\),那么

\[f[x][1]=\sum\min\{f[v][0],f[v][1]\}+\min\{f[v][0]-f[v][1]\} \\=\sum f[v][1]+\min\{f[v][0]-f[v][1]\}\ \ \ \ \ \ \ \ \ \ \ \]

也就是说,\(x\)至少要有一个儿子\(v\)在支配集才行,这就很像最大匹配的转移

情况三,\(x\)被父亲\(a\)支配

这说明\(x\)不在支配集,且\(x\)的儿子均不能支配\(x\),也就是儿子都不在支配集中

\[f[x][2]=\sum f[v][1] \]

最小链覆盖

一个点必然被路径覆盖,根据是否为路径的端点分类

\(f[x][0]\)表示以\(x\)为根的子树,\(x\)不为端点的最小路径覆盖数

\(f[x][1]\)表示以\(x\)为根的子树,\(x\)为一条路径端点的最小路径覆盖数

设当前做到了子树\(v\)

\[\begin{align*} f[x][0]&=\min\{f[x][0]+f[v][0],f[x][1]+f[v][1]\}\\ f[x][1]&=\min\{f[x][1]+f[v][0],cnt+f[v][1]\} \end{align*} \]

其中\(cnt\)为之前子树中\(\sum f[pre][0]\)

树形依赖背包

以选课为代表的一种题型,\(n\)个物品,体积\(V_i\),价值\(W_i\),每件物品依赖于另一个物品,依赖关系成森林,背包容量\(V\),求最大价值。

先讨论一个定义:泛化物品

泛化物品:物品价值随着体积而变化

我们可以用一个数组来表示泛化物品的映射关系,如\(G[i]\)表示体积为\(i\)时,这个物品对应的价值

不妨设一个超级源点,把森林接成一棵树

每个子树看成一个泛化物品\(f[i][j]\)代表在以\(i\)为根的子树中,分配\(j\)体积产生的最大收益

对于节点\(x\),我们实际上就是要合并它的子节点\(v\),从而求出\(x\)对应的\(f​\)数组

考虑一下如何合并泛化物品

泛化物品之间的合并

\(G[i]=\max\limits_{j=0}^{i}\left\{G_x[i-j],G_y[j]\right\}\)

我们要枚举\(i\),还要枚举\(j\leq i\),复杂度在\(O(V^2)\)

泛化物品和单件物品的合并

回顾01背包,\(f[j]\)本质上就是一个泛化物品,加入第i件物品的过程就是一个合并过程

\(G[j]=max\left\{ G'[j],G’[j-v]+w\right\}\)

这样的复杂度在\(O(V)\)

泛化物品的并

两个物品存在交集,不能同时取,对同一体积选取价值较大的

\(G[j]=max\left\{ G_x [i],G_y [j]\right\}\)

复杂度\(O(V)​\),可以以此为依据优化树形依赖背包

泛化物品-分组背包

\(f[x][j]\)表示在以\(x\)为根的子树中,选取了\(j\)个物品的最大收益(\(j>0\)\(x\)必选)

那么,这样的\(f[x][j]\)就是一个泛化物品,也可以理解为分组背包

什么意思?投入\(j\)的代价,就有\(f[x][j]\)的价值,这是泛化物品的样子

\(f[x][j]\)分成\(size[x]\)份,即\(f[x][0],f[x][1],f[x][2],···,f[x][size[x]-1],f[x][size[x]]\)

这是一组物品,我们叫第x组,那么在这些物品中只能选一个,且它们的体积分别为\(0,1,2,...,size[x]-1,size[x]\),那么对应价值为\(f[x][0],f[x][1],···,f[x][size[x]]\)

私以为这正是一个分组背包问题,我们做的也是分组背包的事情——每组选一个物品

假设当前考虑到了\(x\)的一个儿子\(v\),那么我们要做的,就是把\(v\)\(x\)合并起来,正是两个泛化物品的合并

注意,这样做的看似复杂度在\(O(n^2m)\),通过卡边界(即子树大小)可以到\(O(nm)\)

DFS序做法

建原树的DFS序,根据树剖的前置知识我们知道,每个子树都对应了DFS序一段连续的区间

每次考虑是否进入子树内部即可,转移顺序?倒着转移就符合拓扑序

	f[i][j]=max(f[i+1][j-v[id[i]]]+w[id[i]],f[i+siz[id[i]]][j]);

显然的,复杂度稳定在\(O(nm)\)

二次扫描与换根法

先从任意一点开始做一次DP,然后考虑树上的一条边,从而第二次DP转移到以每个点为根的状态

+++

区间DP

一般形式

\(f[l][r]\)表示区间\([l,r]\)的某种状态

以区间长度为阶段,枚举左端点确定区间,附加一些区间合并的代价进行转移

本质

如果以每个决策点(转移时的区间划分点)分裂区间,那么会形成一个类似线段树的分治树结构,可以发现区间DP的本质就是在寻找最优的划分方式(划分点的选择,划分顺序的先后)

对于一些一眼看起来是区间DP的题,可能本质上是一个线性DP,如 [APIO2014]序列分割

优化

部分可以采用四边形不等式优化至\(O(n^2)\),见后文

+++++

*状压DP

往往与集合有关,而表示集合的方法就是二进制压位了

操作二进制最方便的方式就是用位运算,但是位运算的优先级是一个坑

最好的方法就是多加括号

技巧

子集枚举,复杂度\(O(3^n)\)

for(int S=0;S<(1<<n);S++){ 
    for(int i=S;i;i=(i-1)&S){
        ...
    } 
}

状态的稀疏性

当合法状态很少时,可以预处理出所有合法状态,从而大大降低常数

bitset

相当于压缩32个bool,bitset可以帮我们乘以一个\(\dfrac1{32}\)的常数,无论是空间还是时间

递推顺序

合理的顺序递推,可以减少复杂度

  • 如统计\([1,n]\)中每个数二进制下1的个数,可以
cnt[x]=cnt[x>>1]+(x&1);
  • 如统计一个集合中元素权值和,可以
sum[S]=sum[S^lowbit(S)]+sum[lowbit(S)];

+++

*数位DP

  1. 前缀和差分得区间(基本适用)
  2. 分类讨论,细心讨论边界
  3. 暴力程序好写,但不对拍基本要完

先预处理一些不带限制的情况,然后从高位到低位填数

一旦填了一个比上限小的数位,就可以立刻通过预处理数组累加答案

实现上推荐采用记忆化搜索,这种题不会卡常数,记搜不仅写起来方便,而且省去了冗余状态

状态中往往要考虑填到了第几位,是否可以随便填(意味着可以使用记忆化数组),以及一些附加信息

+++

*概率期望DP

条件概率

\(P(B\mid A)\)表示A发生的前提下,B发生的概率

计算?设\(a,b,c,d\)

\(a=P(\)AB都发生\()\) \(b=P(\)AB都不发生\()\)

\(c=P(\)只有A发生\()\) \(d=P(\)只有B发生\()\)

那么有\(P(B\mid A)=\dfrac{a}{a+c}=\dfrac{P(AB)}{P(A)}\)

全概率公式

\(P(A)=\sum\limits_{i=1}^{m}P(B_i)\times P(A\mid B_i)\)

理解为对所有导致它发生的事件加权求和

叶贝斯公式

可以由\(P(A\mid B)\)算出\(P(B\mid A)\)

\(P(A\mid B)=\dfrac{P(AB)}{P(B)}\) \(P(B\mid A)=\dfrac{P(AB)}{P(A)}\)

*联立可得\(P(B\mid A)=\dfrac{P(A\mid B)\times P(B)}{P(A)}\)

可以把全概率公式代入分母,就有了

\(P(B\mid A)=\dfrac{P(A\mid B)\times P(B)}{\sum\limits_{i=1}^m{P(B_i)\times P(A\mid B_i)}}\)

期望

\(E(x)=\sum_{y}P(y)*Val(y)\),也就是每个事件发生的概率乘以权值

性质

\(C\)为一常数

  1. \(E(C)=C\)
  2. \(E(Cx)=C\times E(x)\)
  3. \(E(x+y) = E(x) +E(y)\),因此我们可以分开考虑各部分的贡献
  4. \(x,y\)独立,即 \(x\cap y=\empty\),则\(E(xy)=E(x)\times E(y)\)

怎么做

概率通常正推

多个终点一个起点,就用\(f[i]\)表示,从\(i\)到终点的期望步数,\(f[s]\)即为答案

多个起点一个终点,就用\(f[i]\)表示,从起点到\(i\)的期望步数,\(f[t]\)即为答案

设一个DP状态,代表到达这个状态的期望,那么我们可以由 \(\sum\)期望+(代价*概率)转移到新的期望

如果转移状态成环(如图上的随机游走),需要用高斯消元

但对于一些特殊的情况,如树上的问题,那些环往往可以消掉

套路是一颗坚信能解出来的心和待定系数法,之后可以愉快DFS

参考LOJ #2542. 「PKUWC 2018」随机游走

+++

连通性DP

不补充

+++

DP优化

常规优化

常用操作有:预处理和分阶段DP

奇怪优化

卡常不是为了骗分,是为了得到应得的分数

寻址优化

对于高维数组,用一个指针存上次的位置,直接加偏移量

或者把高维数组压成一维,数组维数越高效果越明显

高速缓存命中

遍历数组步长尽量为1,尤其是高维数组

避免开2的整次幂的大数组,速度相差可能有百倍

数据结构优化

前缀和优化

当DP式明显地依赖一段固定区间时,就可以记录一下前缀和/后缀和,转移优化一个\(O(n)\)

LIS问题

经典的LIS有树状数组解法,复杂度同样在\(O(nlogn)\)

定义\(f[i]\)为以第\(i\)结尾的LIS长度

考虑做到了第\(i\)位,我们要做的就是找一个位置\(j\),使得$val[j]<val[i] \(,然后用\)f[j]+1\(尝试更新\)f[i]$

原本我们要循环枚举\(1\leq j < i\),依次判断

现在我们开一个权值线段树,叶子节点上维护对应的\(f[i]\),我们要找的就是权值小于\(val[i]\)的位置上的最大值

这个最大值很好维护,可以在\(O(logn)\)的时间求出,然后我们把\(f[i]\)插入到\(val[i]\)的位置

实际中,我们可以先离散化一下\(val\)数组,然后可以用静态的树状数组去做,常数较小

RMQ形式的转移

上述LIS问题,优化的瓶颈在于求RMQ

当转移区间单调移动,我们可以采用单调队列进行优化,后文介绍

若不单调移动,则可以用线段树做到\(O(logn)\)的转移

本质上是在做偏序问题

1D1D优化

决策单调性

形如\(f[i]=\min\limits_{j=1}^{i-1}\{f[j]+w[j][i]\}\)的方程,若\(w[i][j]\)满足\(w[i][j]+w[i+1][j+1]\le w[i+1][j]+w[i][j+1]\)

则对于决策点\(K[i]\)有 $K[i-1] \le K[i] $

单调栈优化

显然,我们可以在转移时从\(K[i-1]\)开始枚举,但是这样做仍然是\(O(n^2)\)的(考虑 K[1~n-1]均为1,K[n]=n-1

因此,与其考虑怎么转移,倒不如考虑当前\(K[i]\)可以更新哪些状态

由于决策单调,每次覆盖的一定是一个完整的后缀

所以一定存在一位置\(pos\),使得\(pos\)之前的f[i]由\(K[i]\)决策不优,\(pos\)之后的则优,这就具有单调性了

因此我们用一个单调栈/单调队列来维护每个转移点覆盖的区间

加入一个新的转移点,就从栈顶开始比较,若当前转移优于栈顶转移点开始位置的转移,则弹栈,并加上这段区间

否则,分界点一定在这个区间内,在这个区间内二分寻找分界点

复杂度\(O(nlogn)\)

\(PS​\):我们称满足该性质的二元函数为凸函数

分治

特别地,形如\(f[i]=\min\limits_{j=1}^{i-1}\{g[j]+w[j][i]\}\)的方程,若\(w\)为凸函数,且\(f\)\(g\)无关

那么,我们可以使用分治,非常便捷地完成这个转移

具体怎么做?

void solve(int l,int r,int L,int R){//当前处理区间[l,r],取值区间在[L,R]
    int mid=(l+r)>>1;
    for(int i=L;i<=R;i++)
        计算f[mid],记录转移点为pos;
    solve(l,mid-1,L,pos);
    solve(mid+1,r,pos,R);
}

这样仍然是\(O(nlogn)\)的,离线

一种特殊形式

从一个特殊形式下手,若决策单调性中\(w\)满足\(w[i][j]+w[j][k]==w[i][k]\),那么我们可以把\(w\)拆成新的一元函数\(g\),使得\(g[j]- g[i]==w[i][j]\),类似前缀和

那么,我们转移可以略做改动

\(f[i]=\min\limits_{j=1}^{i-1}\{f[j]+g[j]-g[i]\}\)\(=\min\limits_{j=1}^{i-1}\{h[j]\}+g[i]\)

其中\(h[j]=f[j]-g[j]\),对于已知的\(j\),这是一个常量
这样的式子是没有什么意义的,因为这就是\(h\)的前缀最小值,我们可以用一个变量存下来,这却启发我们进一步探索

单调队列

维护一个队列,其下标递增,元素按值有序,且下标之差不超过\(len\),可以方便地解决\(sliding \space window\)的问题,即连续区间中的最值问题

实现上,单调队列存的是下标

它可以将\(1D/1D\)的DP优化掉\(O(n)\)的复杂度,相比RMQ要快一个\(log\)

如何优化

上文我们提到了 \(f[i]=\min\limits_{j=1}^{i-1}\left\{h[j]\right\}+g[i]\),这是一种平凡的转移

但是,若给范围加上限制呢?

形如\(f[i]=\min\limits_{j=b[i]}^{i-1}\{h[j]\}+g[i]\)的方程,其中 $b[i]\ \(随\)\ i\ $的增大而不降

这就是每次在求\(i\)往前的一段\(h[j]\)中的最小值了,我们当然可以用各种\(log\)级的数据结构维护,实现\(O(nlogn)\)的转移,这样忽略了\(b[i]\)不降这一个很强的性质

单调移动连续区间的最值问题,正是单调队列擅长的 !

因此我们可以维护一个单调队列,队头根据\(b[i]\)调整即可,复杂度\(O(n)\)

单调队列维护的值,视情况而定,有时候是dp数组本身,有时候是转移的数组

应用

滑动窗口

假设求每个长度为m的区间的最大值,那么我们的方程可以写为

\(f[i]=\max\limits_{j=i-m+1}^i \{val[j]\}\)

这实在是上文形式的一种特殊表现,其中\(g[i]=0\)\(b[i]=i-m+1\)

基环树直径

之前提到,直径必然存在子树内或子树之间

子树内用树形DP求出直径后,破环成链

\[h[i]=\max_{j=i-n+1}^i\{f[j]+dis[j][i]\} \]

其中\(f[x]\)表示\(x\)向子树延伸的最长链

可以拆分\(dis​\)为单变量函数,注意到下标范围是一个滑动窗口,采用单调队列优化

更快的多重背包

写出朴素的转移

\(f[i]=\max\limits_{j=1}^{t[i]} \left\{ f[i-v[i]\times j]+w[i]\times j \right\}\)

我们决策的区间不连续,它们间隔着若干个\(v[i]\),怎么做呢?

我们在模\(v[i]\)的意义下进行DP,这样决策就是连续的区间了,但是为了修正体积造成的价格影响,我们改用\(f[j*v[i]+d]-j*w\)代替原来的价值入队

可以方便地应用单调队列优化至\(O(nm)\)

题外话:

注意这个背包的形式,当\(t[i]\)=1或者$t[i]=+\infty $时,是我们会做的01背包或完全背包,在这个式子中有什么意义呢?

\(t[i]=1\),那么只决策一次,就是01背包

\(t[i]=+\infty\),等于去掉了上界,变成了最值问题,我们依然可以用一个变量来存

斜率优化

形如\(f[n]=\min\limits_{i=1}^{n-1} \{a[n]\times X[i]+b[n]\times Y[i]\}+C\)的式子,其中\(a[n]\)\(b[n]\)与决策无关,C为常数

常数不管,直接加上,只考虑如何优化\(min\)部分的决策

对于一个已经求出的\(f[n]\),它一定是由之前某个\(a[n]\times X[k]+b[n] \times Y[k]\)转移而来

由于\(a[n]​\)\(b[n]​\)是一个定值,与决策无关,我们可以认为\((X[k],Y[k])​\)决定了\(f[n]​\),也就是\(f[n]​\)\((X[k],Y[k])​\)一一对应

注意到\((X[k],Y[k])\)的形式非常像平面点对,我们称这样的点为一个决策点,不妨以\(X[i]\)\(x\)轴,\(Y[i]\)\(y\)轴,建立平面直角坐标系

然后我们要最小化\(f[n]=a[n]\times X[i]+b[n]\times Y[i]\),这是一个线性规划问题,移项可得

\(Y[i]=-\dfrac{a[n]}{b[n]}{\times X[i]} +\dfrac{f[n]}{b[n]}\)

这正是直线\(y=kx+b\)的形式

假设\(a[n],b[n]\!>\!0\)(反之同理),那么我们要最小化\(f[n]\!\),也就是最小化\(\!\dfrac{f[n]}{b[n]}\),也就是这条直线与\(y\)轴交点的纵坐标(纵截距)

想象一条直线,自下而上地扫描,那么碰到的第一个点,就是最优的决策点,由此时直线的纵截距可算出最优的\(f\)

这样的点对一定构成了一个平面上的凸包,只要能快速维护凸包,就能快速决策

单调的情形

如果加入点的坐标具有单调性,那么凸包的形态也是“单调”的

如果恰好直线的斜率也具有单调性,决策点的位置也是单调移动的

我们可以用单调队列/单调栈维护决策点,好写

不单调的情形

平衡树/CDQ维护凸壳

强烈推荐CDQ,因为DP转移不需要在线,相比复杂的Splay,CDQ好写很多

四边形不等式

形如\(f[i][j]=min/max\{f[i][k]+f[k+1][j]+w[i][j]\}\)的方程,若\(w\)满足\(w[a,d]+w[b,c]\le w[a,c]+w[b,d]\),则可以使用四边形不等式优化转移

这是区间DP枚举断点转移的形式之一,本身要枚举三层:长度,左端点,断点,复杂度\(O(n^3)​\)

借助四边形不等式,可以把内层枚举断点做到均摊\(O(1)\),从而实现\(O(n^2)\)的转移。

\(K[i][j]​\)数组,代表转移到\([i,j]​\)区间时所对应的转移点(断点),那么内层循环可以写成

for(int k=K[i][j-1];i<=K[i-1][j];k++){
    ...
}

我们以长度为阶段进行转移,所以长度为\(len-1\)\(K\)数组已经计算过了

难点在于发现这一特殊的单调性,虽然优化效果非常明显,但是使用范围比较窄

四边形不等式成立,当且仅当\(w[i][j]+w[i+1][j+1]\le w[i+1][j]+w[i][j+1]\),即w为二元凸函数

事实上,这样的\(w\)函数在一维的状态中满足决策单调性

如果小于等于改大于号呢?

矩阵优化递推

若所求的n极大,可以考虑采用矩阵快速幂优化DP

实际上这是递推而不是动规,因为它显然无法处理\(\min/\max\)之类的决策

思想类似经典的求斐波那契第\(n\)项,构造转移矩阵是关键

复杂度通常在\(O(k^3\log_2n)\),其中\(k\)为转移矩阵规模

还有一种技巧是二分答案+DP验证

矩阵优化DP

immortalCO在WC2018上的PPT

这种优化不是常规意义上的优化,是带修DP的优化,也就是动态DP

图论中邻接矩阵幂是具有实际含义的,同样具有矩阵的性质,因此可以进行快速幂

这样的矩阵,相当于加改\(\min/\max\),乘改加,对应的单位矩阵就是主对角线全0,其余\(\infty\)\(-\infty\)

我们用线段树维护区间矩阵幂,就平衡了修改和询问的复杂度

树上怎么做?重链剖分可以帮我们转化为链上问题

posted @ 2018-10-20 00:04  GhostCai  阅读(99)  评论(1)    收藏  举报