前言.
这篇文章不贴代码,只是介绍最基本的DP模型,基本都是列出基本的状态和转移方程或者导向另一篇题解.
约定:
1.记
[
a
]
[a]
[a]表示判定表达式
a
a
a是否为真,为真则值为
1
1
1,为假则值为
0
0
0.
2.记
f
←
g
f\leftarrow g
f←g表示状态
g
g
g会对状态
f
f
f产生贡献,自己对自己的贡献可能会不写.
3.记
f
a
k
fa_{k}
fak表示树上
k
k
k的父亲节点,
s
o
n
k
son_{k}
sonk表示
k
k
k的子节点集合.
4.记
s
i
z
k
siz_{k}
sizk表示树上
k
k
k的子树大小.
5.记
V
V
V表示图的点集,
E
E
E表示图的边集,
(
x
,
y
)
(x,y)
(x,y)代表一条从
x
x
x连向
y
y
y的边.
6.对于一张带权图,记
v
(
x
,
y
)
v(x,y)
v(x,y)表示边
(
x
,
y
)
(x,y)
(x,y)的边权.
7.对于一张无向图,记
d
e
g
k
deg_{k}
degk为点
k
k
k的度.
一.线性DP.
就是最简单的DP入门,基本没有固定的模板,主要在于状态的设计,这里只介绍几个基本的模型.
最长上升子序列:求一个长度为 n n n的序列 a i a_i ai的最长的子序列满足这个子序列严格递增.
设
f
[
i
]
f[i]
f[i]表示前
i
i
i个数必须选第
i
i
i个的最长上升子序列长度,则有(假装有个
a
i
=
−
∞
a_i=-\infty
ai=−∞,初始
f
[
0
]
=
0
f[0]=0
f[0]=0):
f
[
i
]
=
max
0
≤
j
≤
i
∧
a
j
<
a
i
{
f
[
j
]
}
+
1
f[i]=\max_{0\leq j\leq i\wedge a_j<a_i}\{f[j]\}+1
f[i]=0≤j≤i∧aj<aimax{f[j]}+1
时间复杂度 O ( n 2 ) O(n^{2}) O(n2).
可以研究其单调性用二分优化,也可以转化为二维偏序用树状数组优化.
最长公共子序列:求一个长度为 n n n的序列 a i a_i ai的最长的子序列满足其也是一个长度为 m m m的序列 b i b_i bi的子序列.
设
f
[
i
]
[
j
]
f[i][j]
f[i][j]表示
a
a
a中前
i
i
i个数和
b
b
b中前
j
j
j个数的最长公共子序列,则有:
f
[
i
]
[
j
]
=
{
f
[
i
−
1
]
[
j
−
1
]
+
1
a
i
=
b
j
max
{
f
[
i
−
1
]
[
j
]
,
f
[
i
]
[
j
−
1
]
}
a
i
≠
b
j
f[i][j]= \left\{\begin{matrix} f[i-1][j-1]+1&a_i=b_j\\ \max\{f[i-1][j],f[i][j-1]\}&a_i\neq b_j \end{matrix}\right.
f[i][j]={f[i−1][j−1]+1max{f[i−1][j],f[i][j−1]}ai=bjai=bj
时间复杂度 O ( n m ) O(nm) O(nm).
最长公共上升子序列:求一个长度为 n n n的序列 a i a_i ai和一个长度为 m m m的序列 b i b_i bi的最长的公共子序列满足这个子序列严格递增.
设
f
[
i
]
[
j
]
f[i][j]
f[i][j]表示
a
a
a中前
i
i
i个数和
b
b
b中前
j
j
j个数组成的满足条件的子序列的最长长度,其中
b
j
b_j
bj必须选,则有:
f
[
i
]
[
j
]
=
{
f
[
i
−
1
]
[
j
]
a
i
≠
b
j
max
0
≤
k
<
j
,
b
k
<
a
i
{
f
[
i
−
1
]
[
k
]
}
+
1
a
i
=
b
j
f[i][j]= \left\{\begin{matrix} f[i-1][j]&a_i\neq b_j\\ \max_{0\leq k<j,b_{k}<a_i}\{f[i-1][k]\}+1&a_i=b_j \end{matrix}\right.
f[i][j]={f[i−1][j]max0≤k<j,bk<ai{f[i−1][k]}+1ai=bjai=bj
直接实现是 O ( n m 2 ) O(nm^{2}) O(nm2)的.
不过在 i i i确定的时候, a i a_i ai成为了一个定值,此时我们可以在枚举 j j j的同时维护一个变量表示所有满足 b k < a i b_k<a_i bk<ai的 f [ i − 1 ] [ k ] f[i-1][k] f[i−1][k]的最小值,时间复杂度优化到 O ( n m ) O(nm) O(nm).
有些时候DP会和一些贪心的想法结合起来,最典型的例子便是一道叫养猪的被洛谷抛弃的题.
养猪:给定 n n n个物品,第 i i i个物品的价值为 v i v_i vi,且每天价值下降 p i p_i pi,若每天你只能得到最多一个物品的价值,问 m m m天内你能够获得的最大价值.
首先根据贪心的想法,若你选择的物品是 a 1 , a 2 , a 3 , ⋯ , a k a_1,a_2,a_3,\cdots,a_k a1,a2,a3,⋯,ak,则你肯定会在第 i i i天选择 p a j p_{a_j} paj第 i i i大的,所以先按照 p i p_i pi排序预处理一下.
设
f
[
i
]
[
j
]
f[i][j]
f[i][j]表示前
i
i
i个物品选择
j
j
j个的方案数,则有:
f
[
i
]
[
j
]
=
max
{
f
[
i
−
1
]
[
j
]
,
f
[
i
−
1
]
[
j
−
1
]
+
a
i
−
(
j
−
1
)
p
i
}
f[i][j]=\max\{f[i-1][j],f[i-1][j-1]+a_i-(j-1)p_i\}
f[i][j]=max{f[i−1][j],f[i−1][j−1]+ai−(j−1)pi}
时间复杂度
O
(
n
(
m
+
log
n
)
)
O(n(m+\log n))
O(n(m+logn)).
二.记忆化搜索.
有一些DP的状态层次难以划分或转移方程过于复杂,直接用递推的形式难以实现,此时可以通过搜索的方式实现,即每个状态搜索过一次后就记录下当前值,不再重复计算.
这就是一种称之为记忆化搜索的DP实现技巧.
一道例题:luogu1434滑雪.
三.背包DP.
这是一类非常经典的模型,只介绍几个最基本的模型.
01背包:给定一个容量为 m m m的背包和 n n n个物品,每个物品有一个重量 w i w_i wi和价值 v i v_i vi,求塞到背包中的物品最大价值和.
设
f
[
i
]
[
j
]
f[i][j]
f[i][j]表示前
i
i
i个物品总重量为
j
j
j的最大价值和,则有:
f
[
i
]
[
j
]
=
max
{
f
[
i
−
1
]
[
j
]
,
f
[
i
−
1
]
[
j
−
w
i
]
+
v
i
}
f[i][j]=\max\{f[i-1][j],f[i-1][j-w_{i}]+v_{i}\}
f[i][j]=max{f[i−1][j],f[i−1][j−wi]+vi}
时空复杂度为 O ( n m ) O(nm) O(nm).
不过我们其实可以把空间复杂度优化到 O ( m ) O(m) O(m),即滚动数组去掉第一维,并倒序枚举 j j j即可.
完全背包:给定一个容量为 m m m的背包和 n n n种物品,每种物品有一个重量 w i w_i wi和价值 v i v_i vi,且每种物品的数量是无限的,求塞到背包中的物品最大价值和.
同样设
f
[
i
]
[
j
]
f[i][j]
f[i][j]表示前
i
i
i种物品总重量为
j
j
j的最大价值和,则有:
f
[
i
]
[
j
]
=
max
{
f
[
i
−
1
]
[
j
]
,
f
[
i
]
[
j
−
w
i
]
+
v
i
}
f[i][j]=\max\{f[i-1][j],f[i][j-w_{i}]+v_{i}\}
f[i][j]=max{f[i−1][j],f[i][j−wi]+vi}
将上面优化空间的方法中 j j j的枚举顺序变为正序即可优化空间复杂度为 O ( m ) O(m) O(m).
多重背包:给定一个容量为 m m m的背包和 n n n种物品,第 i i i种物品的重量为 w i w_i wi,价值为 v i v_i vi,数量为 c i c_i ci,求塞到背包中的物品最大价值和.
设
f
[
i
]
[
j
]
f[i][j]
f[i][j]表示前
i
i
i种物品总重量为
j
j
j的最大价值和,则有:
f
[
i
]
[
j
]
=
max
k
=
0
c
i
{
f
[
i
−
1
]
[
j
−
k
w
i
]
+
k
v
i
}
f[i][j]=\max_{k=0}^{c_i}\{f[i-1][j-kw_i]+kv_i\}
f[i][j]=k=0maxci{f[i−1][j−kwi]+kvi}
直接实现的时间复杂度为 O ( n m 2 ) O(nm^{2}) O(nm2),可以直接使用单调队列优化到 O ( n m ) O(nm) O(nm),但这里不介绍这种做法.
另一种做法是把这个问题转化为01背包,即把每种物品拆分为 c i c_i ci个物品直接跑,这样做的时间复杂度仍为 O ( n m 2 ) O(nm^{2}) O(nm2).
不过我们发现不需要拆成 c i c_i ci个物品,可以直接把它拆分成 1 + 2 + 4 + ⋯ + 2 j + k = c i ( k < 2 j ) 1+2+4+\cdots +2^{j}+k=c_i(k<2^{j}) 1+2+4+⋯+2j+k=ci(k<2j)的形式,再进行01背包.
时间复杂度 O ( n m log m ) O(nm\log m) O(nmlogm).
可行性多重背包:给定一个容量为 m m m的背包和 n n n种物品,每种物品有一个重量 w i w_i wi和数量 c i c_i ci,求是否可以刚好塞满背包.
与上题相同,是一个多重背包模板,但是现在是判定可行性,是否可以根据其特性进行优化呢?
我们新增一个状态 g [ i ] g[i] g[i]表示在当前物品的转移中,要使重量 i i i可行需要当前物品的数量最少是多少,这个时候就可以做到 O ( m ) O(m) O(m)转移一个物品了.
时间复杂度 O ( n m ) O(nm) O(nm).
容量超大价值很小的背包:给定一个容量为
m
m
m的背包和
n
n
n个物品,每个物品有一个重量
w
i
w_i
wi和价值
v
i
v_i
vi,求塞到背包中的物品最大价值和.
1
≤
m
,
w
i
≤
1
0
9
,
1
≤
n
,
v
i
≤
300
1\leq m,w_i\leq 10^9,1\leq n,v_i\leq 300
1≤m,wi≤109,1≤n,vi≤300.
设 f [ i ] [ j ] f[i][j] f[i][j]表示前 i i i个物品价值和为 j j j的最小总重量进行DP即可.
时间复杂度 O ( n ∑ v i ) O(n\sum v_i) O(n∑vi)
分组背包: 给定一个容量为 m m m的背包和 n n n组物品,第 i i i组物品包含 c i c_i ci个物品,第 j j j个物品重量为 w i , j w_{i,j} wi,j,价值为 v i , j v_{i,j} vi,j,求每组只选至多一个塞到背包中的物品最大价值和.
设
f
[
i
]
[
j
]
f[i][j]
f[i][j]表示前
i
i
i组重量为
j
j
j的最大价值和,则有:
f
[
i
]
[
j
]
=
max
{
f
[
i
−
1
]
[
j
]
,
max
k
=
1
c
i
{
f
[
i
−
1
]
[
j
−
w
i
,
k
]
+
v
i
,
k
}
}
f[i][j]=\max\{f[i-1][j],\max_{k=1}^{c_i}\{f[i-1][j-w_{i,k}]+v_{i,k}\}\}
f[i][j]=max{f[i−1][j],k=1maxci{f[i−1][j−wi,k]+vi,k}}
时空复杂度 O ( m ∑ c i ) O(m\sum c_i) O(m∑ci),可以用上面的方法将空间复杂度降为 O ( m ) O(m) O(m).
差值DP:给定一个容量为 m m m的背包和 n n n个物品,每个物品有一个重量 w i w_i wi和两种价值 v i , 0 , v i , 1 v_{i,0},v_{i,1} vi,0,vi,1,现在限定每种物品只能有选 0 / 0/ 0/选 1 / 1/ 1/不选三种状态,且选 0 0 0的物品数应该大于选 1 1 1的物品数,求塞到背包中的物品最大价值和.
设 f [ i ] [ j ] [ k ] [ t ] f[i][j][k][t] f[i][j][k][t]表示前 i i i个物品重量和为 j j j,物品 0 0 0选 k k k个且物品 1 1 1选 t t t个的最大价值和,则可以在 O ( n 3 m ) O(n^{3}m) O(n3m)的时间复杂度内解决该问题.
但是我们现在只要求物品 0 0 0的数量与物品 1 1 1的数量的大小关系确定,所以说我们可以把 k k k和 t t t这两维用它们的差值 k − t k-t k−t代替,那么DP状态就少了一维,时间复杂度降为 O ( n 2 m ) O(n^{2}m) O(n2m).
注意这个时候会有负数下标,所以需要偏移量.
带删除背包方案数:给定一个容量为
n
n
n的背包和
m
m
m次操作,操作有如下几种:
1.加入一个物品,重量为
w
w
w.
2.删除一个物品,重量为
w
w
w,保证这个重量在背包中还有.
3.查询装满背包的方案数.
直接用01背包的方法,设 f [ i ] f[i] f[i]表示当前物品重量和为 i i i的方案数,那么加入的时候就倒序枚举 i i i转移 f [ i ] ← f [ i ] + f [ i − w ] f[i]\leftarrow f[i]+f[i-w] f[i]←f[i]+f[i−w],但删除时怎么办?
考虑加入的顺序是没有关系的,那么删除的时候也可以直接正序枚举 i i i并转移 f [ i ] ← f [ i ] − f [ i − w ] f[i]\leftarrow f[i]-f[i-w] f[i]←f[i]−f[i−w].
时间复杂度 O ( n m ) O(nm) O(nm).
限制物品数的背包:给定一个容量为 m m m的背包和 n n n种物品,第 i i i种物品重量为 i i i且数量无限,要求选的物品数不超过 k k k个,求装满背包的方案数.保证 m ≤ n m\leq n m≤n.
设
f
[
i
]
[
j
]
f[i][j]
f[i][j]表示
i
i
i个物品重量为
j
j
j的方案数,则有:
f
[
i
]
[
j
]
=
f
[
i
−
1
]
[
j
−
1
]
+
f
[
i
]
[
j
−
i
]
f[i][j]=f[i-1][j-1]+f[i][j-i]
f[i][j]=f[i−1][j−1]+f[i][j−i]
这两种转移分别表示把增加一个重量为 1 1 1的物品和把之前所有物品重量 + 1 +1 +1.
背包队列:给定一个队列和背包容量
n
n
n,要求支持
m
m
m次操作,操作有如下几种:
1.从队头加入一个物品,重量为
w
w
w,价值为
v
v
v.
2.从队尾删除一个物品.
3.查询最大价值和.
考虑用两个栈模拟队列,若队尾的栈空了后还要删除,就把队头的栈中所有物品大力放入队尾的栈.
时间复杂度
O
(
n
m
)
O(nm)
O(nm).
四.区间DP.
区间DP是一类特殊的线性DP,以区间为状态,即 f [ l ] [ r ] f[l][r] f[l][r]表示区间 [ l , r ] [l,r] [l,r]的答案.
石子合并:有 n n n堆石子顺序排列,第 i i i堆有 a i a_i ai颗石子,每次只能合并相邻两堆,此时耗费的力气为这两堆的石子数之和,求合并成一堆花费的力气最小值.
设
f
[
l
]
[
r
]
f[l][r]
f[l][r]表示合并第
l
l
l堆到第
r
r
r堆花费的力气最小值,则有:
f
[
l
]
[
r
]
=
min
i
=
l
r
−
1
{
f
[
l
]
[
i
]
,
f
[
i
+
1
]
[
r
]
}
+
∑
i
=
l
r
a
i
f[l][r]=\min_{i=l}^{r-1}\{f[l][i],f[i+1][r]\}+\sum_{i=l}^{r}a_i
f[l][r]=i=lminr−1{f[l][i],f[i+1][r]}+i=l∑rai
时间复杂度 O ( n 3 ) O(n^{3}) O(n3).
区间DP有一种拓展,即环状区间DP,一般的做法就是倍长原序列.
环状石子合并:有 n n n堆石子环状排列,第 i i i堆有 a i a_i ai颗石子,每次只能合并相邻两堆,此时耗费的力气为这两堆的石子数之和,求合并成一堆花费的力气最小值.
我们直接倍长原来的 a i a_i ai,区间DP后取所有 f [ i ] [ i + n − 1 ] f[i][i+n-1] f[i][i+n−1]中的最小值作为答案输出即可.
时间复杂度 O ( n 3 ) O(n^{3}) O(n3).
有时候区间DP还可以做一些给定树的某个顺序,求使得树最优或树计数的题.
树方案统计:一棵树上每个点都有一个颜色,现在dfs遍历并记录颜色得到一个长度为 2 n 2n 2n的颜色序列 a i a_i ai(进出均记录),统计这棵树形态的方案数.
设 f [ l ] [ r ] f[l][r] f[l][r]表示区间 [ l , r ] [l,r] [l,r]内的颜色可以构成的树的方案数,那么转移时为了不重复,我们需要枚举第一棵子树代表的区间,但此时我们没有办法快速统计第一棵子树外的其它几棵子树一起的总方案数,这样子的状态设计失败了.
设
f
[
l
]
[
r
]
f[l][r]
f[l][r]表示区间
[
l
,
r
]
[l,r]
[l,r]内的颜色可以构成的森林的方案数,那么答案为:
{
0
a
1
≠
a
n
f
[
2
,
n
−
1
]
a
1
=
a
n
\left\{\begin{matrix} 0&a_1\neq a_n\\ f[2,n-1]&a_1=a_n \end{matrix}\right.
{0f[2,n−1]a1=ana1=an
此时转移就可以这样:
f
[
l
]
[
r
]
=
∑
l
<
i
<
r
∧
a
l
=
a
i
f
[
l
+
1
]
[
i
−
1
]
∗
f
[
i
+
1
]
[
r
]
f[l][r]=\sum_{l< i<r\wedge a_l=a_i}f[l+1][i-1]*f[i+1][r]
f[l][r]=l<i<r∧al=ai∑f[l+1][i−1]∗f[i+1][r]
时间复杂度
O
(
n
3
)
O(n^{3})
O(n3).
五.树形DP.
简单的树形DP通常是设 f [ i ] f[i] f[i]表示以 i i i为根的子树,然后直接把所有儿子合并到父亲身上.
最大独立集:求一棵 n n n个节点的树的点集的一个最大的子集 S S S满足 S S S中不存在两点之间有连边.
设
f
[
i
]
[
0
/
1
]
f[i][0/1]
f[i][0/1]表示以
i
i
i为根的子树点
i
i
i选
/
/
/不选时最大独立集大小,则有:
f
[
k
]
[
0
]
=
∑
y
∈
s
o
n
k
max
{
f
[
y
]
[
0
]
,
f
[
y
]
[
1
]
}
f
[
k
]
[
1
]
=
1
+
∑
y
∈
s
o
n
k
f
[
y
]
[
0
]
f[k][0]=\sum_{y\in son_{k}}\max\{f[y][0],f[y][1]\}\\ f[k][1]=1+\sum_{y\in son_{k}}f[y][0]
f[k][0]=y∈sonk∑max{f[y][0],f[y][1]}f[k][1]=1+y∈sonk∑f[y][0]
时间复杂度 O ( n ) O(n) O(n).
树上最长链:给定一棵 n n n个点的树,求每个点出发的最长链长度.
设 f [ i ] [ 0 / 1 ] f[i][0/1] f[i][0/1]表示从 i i i出发在 i i i的子树内的最长链 / / /次长链,这个数组很好处理.
再设
g
[
i
]
g[i]
g[i]表示从
i
i
i出发在
i
i
i子树外的最长链,则有:
g
[
k
]
=
max
{
g
[
f
a
k
]
,
f
[
f
a
k
]
[
f
[
f
a
k
]
[
0
]
=
f
[
k
]
[
1
]
+
1
]
}
+
1
g[k]=\max\{g[fa_{k}],f[fa_{k}][f[fa_{k}][0]=f[k][1]+1]\}+1
g[k]=max{g[fak],f[fak][f[fak][0]=f[k][1]+1]}+1
时间复杂度 O ( n ) O(n) O(n).
这是一种非常基本的被称为换根DP的树形DP方法,当然有些时候可以直接把两个数组合并起来.
对于一些更加复杂的状态,通常使用的转移方式不是把所有子树一口气合并,而是把子树一棵一棵往当前状态中加入.
树形背包:给定一棵 n n n个节点的树,每个节点 i i i有一个价值 a i a_i ai,若一个节点被选,则它的父亲也必须选,问选择 m m m个点可以得到的最大价值和.
设
f
[
i
]
[
j
]
f[i][j]
f[i][j]表示在点
i
i
i子树中选择
j
j
j个点的最大价值和,则有:
f
[
k
]
[
i
+
j
]
←
f
[
k
]
[
i
]
+
f
[
∀
y
∈
s
o
n
k
]
[
j
]
f[k][i+j]\leftarrow f[k][i]+f[\forall y\in son_{k}][j]
f[k][i+j]←f[k][i]+f[∀y∈sonk][j]
时间复杂度 O ( n m 2 ) O(nm^{2}) O(nm2).
一种优秀的优化方式是,我们改变状态的意义,设 f [ i ] [ j ] f[i][j] f[i][j]表示dfs遍历到 i i i之前的所有点中选择 j j j个点的最大价值和,经过一些处理后就可以做到 O ( n m ) O(nm) O(nm).
详情参考luogu2014选课.
还有一种做法是,每次背包时只枚举到 min { m , s i z k } \min\{m,siz_{k}\} min{m,sizk},即不超过子树大小,可以证明这样做的时间复杂度为 O ( n m ) O(nm) O(nm).
证明:
对于子树
u
u
u和
v
v
v,若
s
i
z
u
≥
m
siz_{u}\geq m
sizu≥m且
s
i
z
v
≥
m
siz_{v}\geq m
sizv≥m,则这样的子树数量不超过
n
m
\frac{n}{m}
mn棵,时间复杂度为
O
(
n
m
)
O(nm)
O(nm).
若
s
i
z
u
≥
m
siz_{u}\geq m
sizu≥m且
s
i
z
v
<
m
siz_{v}<m
sizv<m,则此时
∑
s
i
z
v
≤
n
\sum siz_{v}\leq n
∑sizv≤n,时间复杂度为
O
(
n
m
)
O(nm)
O(nm).
若
s
i
z
u
<
m
siz_{u}<m
sizu<m且
s
i
z
v
<
m
siz_{v}<m
sizv<m,则将它们合并到一棵大小超过
m
m
m的子树的时间复杂度为这棵子树上的点对数量,即
O
(
m
2
)
O(m^{2})
O(m2),而这样的子树总数不超过
n
m
\frac{n}{m}
mn棵,时间复杂度为
O
(
n
m
)
O(nm)
O(nm).
综上,总时间复杂度为
O
(
n
m
)
O(nm)
O(nm).
证毕.
六.有向无环图DP.
一类在有向无环图上的DP,通常的状态设计不会有太多特别的地方,跟线性DP一样搞就行了,只不过此时需要用拓扑排序来划分状态罢了.
DAG路径计数:给定一张 n n n个点 m m m条边的DAG,求它上面的路径数量.
设
f
[
i
]
f[i]
f[i]表示到点
i
i
i的路径总数,则有:
f
[
i
]
=
1
+
∑
(
j
,
i
)
∈
E
f
[
j
]
f[i]=1+\sum_{(j,i)\in E}f[j]
f[i]=1+(j,i)∈E∑f[j]
最后再求个和就是答案啦.
时间复杂度 O ( n + m ) O(n+m) O(n+m).
DAG最短路径:给定一张 n n n个点 m m m条边的带权DAG,求从点 1 1 1到点 n n n的最短路径长度.
设
f
[
i
]
f[i]
f[i]表示从点
1
1
1出发到点
i
i
i的最短路径长度,则有:
f
[
i
]
=
min
(
j
,
i
)
∈
E
{
f
[
j
]
+
v
(
j
,
i
)
}
f[i]=\min_{(j,i)\in E}\{f[j]+v(j,i)\}
f[i]=(j,i)∈Emin{f[j]+v(j,i)}
时间复杂度 O ( n + m ) O(n+m) O(n+m).
有一种DAG DP的套路为,先给一个有向图SCC缩点,再在缩点后的图上DAG DP,通常的做题套路也是一样的,只是多了一个SCC缩点的过程.
七.有后效性DP.
有一些DP可以设出状态,可以列出转移方程,却不满足无后效性,即出现了 a → b → a a\rightarrow b\rightarrow a a→b→a的情况.
若此时DP解决的问题是一个最优化问题,此时通常的解决方案是利用最短路对DP进行代替.
最优路径:给定一张 n n n个点 m m m条边的带权有向图,现在需要找到从点 1 1 1到点 n n n的一条路径使得路径上的边权最大值最小.特别的,你可以选择路径上至多 k k k条边把它们的边权变为 0 0 0.
若这是一个DAG上的问题,我们可以直接设
f
[
i
]
[
j
]
f[i][j]
f[i][j]表示到点
i
i
i把
j
j
j条边变为
0
0
0后的最小最大边权,那么有:
f
[
i
]
[
j
]
=
min
(
k
,
i
)
∈
E
{
max
{
f
[
k
]
[
j
]
,
v
(
k
,
i
)
}
,
f
[
k
]
[
j
−
1
]
}
f[i][j]=\min_{(k,i)\in E}\{\max\{f[k][j],v(k,i)\},f[k][j-1]\}
f[i][j]=(k,i)∈Emin{max{f[k][j],v(k,i)},f[k][j−1]}
但是是一张普通的DAG,所以需要用最短路代替DP.
时间复杂度 O ( k ( n + m ) log ( k m ) ) O(k(n+m)\log(km)) O(k(n+m)log(km)).
若此时DP解决的是一个计数问题,通常答案会是无穷,不过当题目能够避免这种情况的时候(比如一个概率问题),一般会用高斯消元来求解.
图上随机游走:给定一张 n n n个点 m m m条边的无向图,每次从一个点出发等概率随机选择一条边走到另一个点,问从点 1 1 1走到点 n n n的期望步数.
可以将问题转化为经过每条边的期望次数后求和,发现这个问题仍不好算,再次转化为经过点的期望次数.
设经过点
i
i
i的期望次数为
f
[
i
]
f[i]
f[i],那么有:
f
[
i
]
=
[
i
=
1
]
+
∑
(
j
,
i
)
∈
E
f
[
j
]
d
e
g
j
f[i]=[i=1]+\sum_{(j,i)\in E}\frac{f[j]}{deg_{j}}
f[i]=[i=1]+(j,i)∈E∑degjf[j]
利用这个转移方程可以列出 n n n个方程组,高斯消元即可解出方程组.需要注意 n n n为终点,直接有 f [ n ] = 1 f[n]=1 f[n]=1.
然后根据
f
[
i
]
f[i]
f[i],我们可以推出答案为:
∑
(
x
,
y
)
∈
E
(
f
[
x
]
d
e
g
x
+
f
[
y
]
d
e
g
y
)
\sum_{(x,y)\in E}\left(\frac{f[x]}{deg_{x}}+\frac{f[y]}{deg_{y}}\right)
(x,y)∈E∑(degxf[x]+degyf[y])
时间复杂度
O
(
n
3
)
O(n^{3})
O(n3).
八.状压DP.
有一些DP需要表示一个集合的状态,例如设 f [ S ] f[S] f[S]表示选集合 S S S的最优解 / / /方案数,此时我们该如何表示呢?
一个最简单的方法是记录
n
n
n个
0
/
1
0/1
0/1,但这样子太麻烦了,为什么不把这
n
n
n个
0
/
1
0/1
0/1压成一个数呢?并且还可以利用计算机中方便的二进制操作进行转移.
拓扑序计数:给定一张 n n n个点 m m m条边的DAG,求其不同拓扑序的数量.
设
f
[
S
]
f[S]
f[S]表示当前拓扑序选入的点集为
S
S
S时的方案数,那么有:
f
[
S
]
=
∑
i
∈
g
(
S
)
f
[
S
−
i
]
f[S]=\sum_{i\in g(S)}f[S-i]
f[S]=i∈g(S)∑f[S−i]
其中 g ( S ) g(S) g(S)表示不能够到达 S S S的点组成的集合.
时间复杂度 O ( 2 n n ) O(2^{n}n) O(2nn).
矩阵取数:给定一个 n ∗ m n*m n∗m的矩阵 a i , j a_{i,j} ai,j,要求取出一些位置使得这些位置互不相邻(四个方向),问可以取得的最大和.
设 f [ i ] [ S ] f[i][S] f[i][S]表示前 i i i行取出数的状态为 S S S时的最大和,然后转移即可.
直接暴力枚举 S 1 S_1 S1和 S 2 S_2 S2的时间复杂度为 O ( 4 m n ) O(4^{m}n) O(4mn).
不过有一个优秀的枚举子集写法如下:
for (int g0=0;g0<1<<n;++g0)
for (int g1=g0;g1;g1=g0&(g1-1))
这是一种用二进制枚举实现不重不漏枚举子集的方法.
这个写法的时间复杂度是 O ( 3 n ) O(3^{n}) O(3n)的,原理是每一个位置只会有三种状态,第四种即 g 0 g_0 g0中没有但 g 1 g_1 g1中有的情况是不会被枚举到的.
然后在枚举 S 1 S_1 S1向某个 S 2 S_2 S2转移的时候,注意到 S 2 S_2 S2必须是 S 1 S_1 S1补集的子集,所以枚举补集的子集即可.
总时间复杂度降为 O ( 3 m n ) O(3^{m}n) O(3mn).