[笔记] 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)\)的时间求出
用来点分治,保证分治的复杂度,相信大家都会正确的点分治
几个性质:
- 一个树最多有两个重心(两个重心时,这两个重心一定是一条边的两个端点)
- 删去重心后剩下的联通块最大的不超过\(\dfrac n2\)
- 重心到所有点的距离之和是树中所有点最小的
树的特性
二分图相关
不要忘记树是严格的二分图
但同时,树有更多优美的性质,可以更快地解决下列问题(贪心/DP)
不带权的问题
事实上,不带权的下列问题完全可以用贪心解决,也可以解决一个后,用二分图性质转化
最小点覆盖=最大匹配=N-最大独立集=N-最大匹配
最小链覆盖=最长反链=N-拆点图的最大匹配
接下来我们不考虑网络流做法/贪心解法,这就必须设计DP状态了
树的最大匹配
设\(f[x][0]\)表示\(x\)为根的子树,\(x\)不和任何一个儿子连的最大匹配数
同理,\(f[x][1]\)表示以\(x\)为根的子树,\(x\)和某个儿子连边后的最大匹配数
转移:
后面的\(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\)均可以选
若\(x\)选,则它的儿子均不可选
最大权独立集:HDU 1520 Anniversary party 类比最大独立集
基环树最大独立集
先对每个子树求出\(f[i][0/1]\)表示以\(i\)为根的子树,选/不选\(i\)点的最大独立集,然后破环成链,做一次链上DP
最小点覆盖
\(f[x][0/1]\)表示考虑以\(x\)为根的子树,且\(x\)不选/选的最小点覆盖
若\(x\)不选,为了覆盖\(x\)到儿子\(v\)的边,\(v\)必选,则有
若\(x\)选,则儿子\(v\)可选可不选,但是要增加1的代价
最小支配集
一个点\(x\),有三种情况,分别考虑一下
- \(x\)在支配集里
- \(x\)不在支配集,且至少被一个儿子\(v\)支配了
- \(x\)不在支配集,且被父亲\(a\)支配了
情况一,既然\(x\)在支配集,那么\(v\)可以任意取,但是要增加1的代价
情况二,\(x\)至少被一个儿子支配了
- 若\(x\)无儿子
- 若\(x\)至少存在一个儿子\(v\),满足\(f[v][0]\leq f[v][1]\),那么
- 若\(x\)的所有儿子均不满足\(f[v][0]\leq f[v][1]\),那么
也就是说,\(x\)至少要有一个儿子\(v\)在支配集才行,这就很像最大匹配的转移
情况三,\(x\)被父亲\(a\)支配
这说明\(x\)不在支配集,且\(x\)的儿子均不能支配\(x\),也就是儿子都不在支配集中
最小链覆盖
一个点必然被路径覆盖,根据是否为路径的端点分类
\(f[x][0]\)表示以\(x\)为根的子树,\(x\)不为端点的最小路径覆盖数
\(f[x][1]\)表示以\(x\)为根的子树,\(x\)为一条路径端点的最小路径覆盖数
设当前做到了子树\(v\)
其中\(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
- 前缀和差分得区间(基本适用)
- 分类讨论,细心讨论边界
- 暴力程序好写,但不对拍基本要完
先预处理一些不带限制的情况,然后从高位到低位填数
一旦填了一个比上限小的数位,就可以立刻通过预处理数组累加答案
实现上推荐采用记忆化搜索,这种题不会卡常数,记搜不仅写起来方便,而且省去了冗余状态
状态中往往要考虑填到了第几位,是否可以随便填(意味着可以使用记忆化数组),以及一些附加信息
+++
*概率期望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\)为一常数
- \(E(C)=C\)
- \(E(Cx)=C\times E(x)\)
- \(E(x+y) = E(x) +E(y)\),因此我们可以分开考虑各部分的贡献
- 若\(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求出直径后,破环成链
其中\(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\)
我们用线段树维护区间矩阵幂,就平衡了修改和询问的复杂度
树上怎么做?重链剖分可以帮我们转化为链上问题
本文来自博客园,作者:GhostCai,转载请注明原文链接:https://www.cnblogs.com/ghostcai/p/9819956.html
浙公网安备 33010602011771号