前言
本文不讲解线段树的基础理论,而是讲解线段树的应用,如线段树优化 DP,扫描线。如有问题欢迎指出。
线段树优化 DP
有的时候,DP 的转移方程的复杂度很高,线段树优化可以将其优化。
以最长上升子序列为例,转移式为:
f
i
=
max
j
=
1
i
−
1
{
f
j
}
(
a
j
<
a
i
)
+
1
f_i = \max_{j = 1}^{i - 1} \{f_j\}(a_j < a_i) + 1
fi=j=1maxi−1{fj}(aj<ai)+1
直接朴素做是
O
(
n
2
)
O(n^2)
O(n2) 的,我们考虑如何优化。
我们发现,转移相当于在之前求的所有 f f f 中找到 1 ≤ a j ≤ a i − 1 1 \le a_j \le a_i - 1 1≤aj≤ai−1 的 f j f_j fj 的最大值。于是就相当于我们在求完 f i f_i fi 后就把 f i f_i fi 插到 a i a_i ai 位置。计算状态时查询 1 1 1 ~ a i a_i ai 的最小值。发现可以使用线段树维护区间 m i n min min, O ( log ( n ) ) O(\log(n)) O(log(n)) 插入和删除,整个 DP 优化至 O ( n log ( n ) ) O(n\log(n)) O(nlog(n))。
这是一个例子。有两种种较为经典的线段树优化 DP 类型:
- 对状态有增加同时有关 i i i 和 j j j 的权值。
- 对状态的转移有限制。
什么意思呢?我们一个一个看。
类型 1
CF1527E - Partition Game。
很容易写出状态转移方程:
f
i
,
j
=
m
i
n
k
<
i
f
k
,
j
−
1
+
l
a
s
t
k
+
1
,
i
−
f
i
r
s
t
k
+
1
,
i
f_{i, j} = min_{k < i} {f_{k, j - 1} + last_{k + 1, i} - first_{k + 1, i}}
fi,j=mink<ifk,j−1+lastk+1,i−firstk+1,i
靠虑如何优化。
发现转移没有限制,但是转移中
l
a
s
t
last
last 和
f
i
r
s
t
first
first 无法直接拆开,可以用线段树维护每一个位置当前
i
i
i 下每个
j
j
j 的值。
考虑每加入一个数,
l
a
s
t
last
last 和
f
i
r
s
t
first
first 有什么变化。
先看
f
i
r
s
t
first
first。找出上一个
a
p
=
a
i
a_p = a_i
ap=ai 的下标
p
p
p。对于所有
≤
p
\le p
≤p 的下标
j
j
j,已经有了第一次出现的位置,无需更改;对于所有
>
p
> p
>p 的下标
k
k
k,第一次出现的位置就是
i
i
i,因此将
[
p
+
1
,
i
−
1
]
[p + 1, i - 1]
[p+1,i−1] 加上
i
i
i。
再看
l
a
s
t
last
last。同样找出上一个
a
p
=
a
i
a_p = a_i
ap=ai 的下标
p
p
p。对于所有
≤
p
\le p
≤p 的下标
j
j
j,已经记录的
p
p
p,而应为
i
i
i,所以将
[
1
,
p
−
1
]
[1, p - 1]
[1,p−1] 先减去
p
p
p 再加上
i
i
i;对于所有
>
p
> p
>p 的下标
k
k
k,最后一次出现的位置就是
i
i
i,将
[
p
+
1
,
i
−
1
]
[p + 1, i - 1]
[p+1,i−1] 加上
i
i
i。
查询时直接查询
[
1
,
i
−
1
]
[1, i - 1]
[1,i−1] 的最大值即可。
区间加区间最大值可以用线段树维护,时间复杂度 O ( k n log ( n ) ) O(kn\log(n)) O(knlog(n))。
对于第 1 种情况,用线段树直接维护转移值即可。
类型 2
最长上升子序列就是一个很好的例子。
将权值作为下标,DP 数组作为权值插入线段树,查询一个区间的值。
一些特殊的线段树优化 DP 类型
有一些 DP 很神奇。
类型 3
CF675E - Trains and Statistic。
设
f
i
f_i
fi 为所有点到
i
i
i 的最小费用之和。发现每次走到当前路线终点,再找到能走的
a
a
a 最大的路线继续走,设新走的路线编号为
k
k
k 则:
f
i
=
f
k
+
(
k
−
i
)
+
(
n
−
a
i
)
f_i = f_k + (k - i) + (n - a_i)
fi=fk+(k−i)+(n−ai)
找到 k k k 相当于在 [ i + 1 , a i ] [i + 1, a_i] [i+1,ai] 中找最大的 k k k,线段树即可,时间复杂度 O ( n log ( n ) ) O(n\log(n)) O(nlog(n))。
本题需要线段树优化 DP,但是线段树和 DP 之间没有过多的联系,只是分开的两步。
练习题
[USACO12OPEN] Bookshelf G
CF1304F2 - Animal Observation (hard version)
扫描线
一个线段树应用的大点,你可以把它理解为从前往后扫,具体扫什么后面会讲。
例题讲解
[SDOI2009] HH的项链
经典扫描线题。
i
i
i 从
1
1
1 到
n
n
n 扫。一个神仙操作是,开一个线段树,每个位置表示当前位当前颜色是不是到
i
i
i 时的最后一个当前颜色。将查询按右端点排序,查询时先让
i
i
i 扫到右端点,然后查询区间和就可以了。
为什么,因为每个位置都是最后一个位置,如果某个颜色算进去,那么由于记录的是最后一个,区间内只有一次,所以不会算重;如果不算进去说明最后一个次颜色在区间前,正常也没有这个颜色。
练习题
P4113 [HEOI2012]采花
P8868 [NOIP2022] 比赛
一些奇妙 trick(不详细讲解)
P5459 [BJOI2016] 回转寿司: 区间和转前缀和。
[ABC342G] Retroactive Range Chmax: 每个区间维护一个 set。
P4588 [TJOI2018] 数学计算: 逆元用不了就使用线段树。