dp 的优化
数据结构优化 dp
没什么好说的,具体题目具体分析。
决策单调性单调队列优化 dp
对于转移方程
f x = min k = p x x − 1 { g k } + w x f_x=\min _{k=p_x}^{x-1}\{g_k\}+w_x fx=k=pxminx−1{gk}+wx
其中 p x p_x px 单调不降。如果存在两个数 j , k j,k j,k 有 j ≤ k j \leq k j≤k 且 g k ≤ g j g_k \leq g_j gk≤gj,那么决策 j j j 是无用的。这意味着我们的最优决策表单调不降(决策单调性),所以可以用一个单调递增的队列进行优化。流程如下
- 队首元素不断出队,直到队首元素在给定范围中,此时队首元素就是状态 f x f_x fx 的最优决策。
- 计算 g x g_x gx,并插入单调队列的尾部,同时维护队列单调递增性。
- 重复上述步骤,可以发现每个状态只入队和出队一次,时间复杂度为 O ( n ) O(n) O(n)。
决策单调性分治优化 dp
如果有决策单调性,当然可以分治了。
我们取当前区间的中点
m
i
d
mid
mid,求一下它的决策点在
k
k
k。那么可以得到
m
i
d
mid
mid 左边的点的决策点一定
≤
k
\leq k
≤k,
m
i
d
mid
mid 右边的点的决策点一定
≥
k
\ge k
≥k,这样递归下去就可以了。
时间复杂度为
O
(
n
log
n
)
O(n \log n)
O(nlogn)。
决策单调性二分栈优化 dp
用一个决策二分栈来维护所有的决策,栈顶是最优的决策。
因为决策单调性,对于栈顶中任意两个决策点,我们都可以通过二分找到一个临界值 k k k,使得在 k k k 之前时其中一个决策转移到 f k f_k fk 更优,而 k k k 之后另一个更优。
所以二分找到当前所有决策点的临界值,如果栈顶的决策达到了临界值就弹出。
斜率优化
直接讲比较迷,拿 HNOI2008 玩具装箱为栗子。
设 f i f_i fi 是装好前 i i i 个玩具的花费,枚举从那个玩具开始放。 O ( n 2 ) O(n^2) O(n2) 的转移方程如下
令 s i = ∑ k = 1 i c k s_i = \sum_{k=1}^i c_k si=∑k=1ick 有
f i = min j = 0 i − 1 f j + ( s i − s j + i − j − 1 − L ) 2 f_i = \min_{j=0}^{i - 1} f_j+(s_i-s_j + i - j - 1 - L)^2 fi=j=0mini−1fj+(si−sj+i−j−1−L)2
再令 s i = ∑ k = 1 i c k + i s_i = \sum_{k=1}^i c_k+i si=∑k=1ick+i, L = L + 1 L = L+1 L=L+1 有
f i = min j = 0 i − 1 f j + ( s i − s j − L ) 2 化简得 f i = s i 2 − 2 s i × L + f j + ( s j + L ) 2 − 2 s i s j 移同类项得 f j + ( s j + L ) 2 = 2 s i s j + f i − s i 2 + 2 s i × L f_i = \min_{j=0}^{i - 1} f_j+(s_i-s_j -L)^2 \\ \text{化简得}\\ f_i=s_i^2-2s_i \times L+f_j+(s_j+L)^2-2s_is_j \\ \text{移同类项得}\\ f_j+(s_j+L)^2 =2s_is_j+ f_i - s_i^2 + 2s_i \times L\\ fi=j=0mini−1fj+(si−sj−L)2化简得fi=si2−2si×L+fj+(sj+L)2−2sisj移同类项得fj+(sj+L)2=2sisj+fi−si2+2si×L
令 y = f j + ( s j + L ) 2 y = f_j+(s_j+L)^2 y=fj+(sj+L)2、 k = 2 s i k = 2s_i k=2si、 x = s j x =s_j x=sj、 b = f i − s i 2 + 2 s i × L b = f_i - s_i^2 + 2s_i \times L b=fi−si2+2si×L。那么得到了
y = k x + b y = kx + b y=kx+b
其中 x x x 满足单调递增性,所以不用对柿子取反。同时, k k k 和关于 j j j 的解析式 y y y 也单调递增,而 b b b 中有要转移的 f i = b + s i 2 − 2 s i × L f_i = b + s_i^2 - 2s_i \times L fi=b+si2−2si×L。
可以发现斜率
k
k
k 是已知的,并且
b
−
f
i
b - f_i
b−fi 也是已知的。如果能求出一个点对
(
x
,
y
)
(x,y)
(x,y),就可以求出
b
b
b,进而求出
f
i
f_i
fi。要让
f
i
f_i
fi 最小就要让
b
b
b 最小,如果把
(
x
,
y
)
(x,y)
(x,y) 展现在坐标系上,我们用一条斜率为
k
k
k 的直线从下向上方移动,第一个在直线上的
(
x
,
y
)
(x,y)
(x,y) 即为需要的点。而我们可以发现,有机会上位的点一定可以组成一个下凸包,即为相邻两个点的斜率是单调递增的,为什么呢?


考虑加一个点得到了直线 k 2 k_2 k2,之前可选的点集中有一条直线 k 1 k_1 k1。如果 k 2 > k 1 k_2 > k_1 k2>k1,那么加的点在以后可能用到,因为随着斜率的单调递增,选的点也逐渐靠上。如果 k 1 < k 2 k_1 < k_2 k1<k2,那么 k 1 k_1 k1 能交的线, k 2 k_2 k2 一定比它先交,所以 k 1 k_1 k1 可以从点集中删除。
所以要维护的点集是一个下凸包。下凸包所有点的斜率是单调递增的,而且下凸包的下方没有其它的点。用单调队列维护。
int l = 1, r = 1;
q[1] = 0, f[0] = 0;
for (int i = 1; i <= n; i++) {
while (l < r && slope(q[l], q[l + 1]) <= k[i]/*斜率*/) l++
int x = q[l];
f[i] = calc(i, x); //转移
while (l < r && slope(q[r - 1], q[r]) >= slope(q[r], i)) r--;
q[++r] = i;
}
有的时候,转移时没有 j < i j < i j<i 的限制,换句话说 f i f_i fi 不从数组 f f f 中得到答案,而是根据数组 g g g 得到答案。那么需要先将提前处理出来的数组 g g g 加入凸包。
注意,斜率优化的条件是“单调”,斜率和 x x x 都要是单调的。如果斜率不单调就不能 pop 掉队首了(当然为了维护下凸性队尾还是要 pop 的),所以可以二分一下。假设要二分的直线斜率为 k k k,那么我们取队列中的 m i d mid mid,如果 m i d + 1 mid + 1 mid+1 与 m i d mid mid 点的斜率 < k < k <k,则 l = m i d + 1 l = mid + 1 l=mid+1,如果 > k >k >k 则 r = m i d − 1 r = mid - 1 r=mid−1,如果都不满足,那么 m i d mid mid 就是要找的点。
如果加入点的 x x x 也不单调怎么办?可以用 splay,但我不会写 splay,不过还是 cdq 分治比较简单。

浙公网安备 33010602011771号