线段树专题II,比I还是好点吧…I的T3T4太毒瘤
T1
[POI2015] KIN
这题思路其实非常的巧妙,至少我想不到
我们考虑从左到右枚举右端点,然后拿线段树储存每个点作为当前选的左端点的答案,最后取
max
\max
max就是当前的答案
然后考虑如何更新答案,我们发现,对于当前右端点
i
i
i位置上的
a
i
a_i
ai,在
a
i
a_i
ai上一次出现的位置
[
l
a
s
t
+
1
,
i
]
[last+1,i]
[last+1,i]之间答案都应该加上
a
i
a_i
ai,飞速码完之后测了一发样例,WA了,为什么呢?
因为我们仔细读定义就会发现,对于在 l a s t last last以前的部分,他是不应该计算答案的,因为如果出现相同的一个都不算,所以我们应该把 [ l a s t 2 + 1 , l a s t ] [last^2+1,last] [last2+1,last]这一段都要减掉 a i a_i ai,emmmmmm l a s t 2 last^2 last2就是 l a s t last last的 l a s t last last啦
然后就好了嗯
T2
[HEOI2016/TJOI2016] 排序
这周集训的第二题,感觉比第三题难想
这道题其实有复杂度为 O ( n log n ) O(n\log n) O(nlogn)的线段树分裂的做法,但是我太菜了不会,所以只能来水两个 log \log log的做法了qwq
我们首先考虑这个区间如果只有 0 0 0和 1 1 1怎么做,那么对于一个区间,如果是升序排序,那么就把区间里面所有的 0 0 0扔到左边, 1 1 1扔到右边,其实就是一个简单的区间覆盖,降序也同理
如果不是 0 , 1 0,1 0,1怎么办呢?考虑把他们变成 0 , 1 0,1 0,1序列,二分一下,把所有小于 Δ \Delta Δ的走变成 0 0 0,其他的变成 1 1 1,然后如果最后 q q q的位置是 0 0 0,就尝试把 Δ \Delta Δ减小(因为 0 0 0太多了…),否则就尝试把 Δ \Delta Δ增大
这样我们就可以在 O ( n log 2 n ) O(n\log^2n) O(nlog2n)的时间解决,因为 n ≤ 1 0 5 n\leq 10^5 n≤105,时限 4 s 4s 4s,显然可以通过
T3
[HAOI2012] 高速公路
先说老师给出的做法吧
显然这题要求的就是区间所有情况的和,因为边不好判断,所以可以把边看成点,那么对于查询一段看成点之后的区间,我们要求的应该就是
∑
i
=
l
r
v
i
t
i
\sum_{i=l}^r v_it_i
∑i=lrviti,其中
t
i
t_i
ti表示第
i
i
i条边被访问的次数
根据乘法原理,就是左边的点数
×
\times
×右边的点数,注意因为看成了点,所以
i
i
i点位置其实左边有一个右边有一个都要被算进去,所以我们知道了我们要求的是
∑
i
=
l
r
(
i
−
l
+
1
)
(
r
−
i
+
1
)
v
i
=
∑
i
=
l
r
(
r
i
−
i
2
+
i
−
l
r
+
l
i
−
l
+
r
−
i
+
1
)
v
i
=
∑
i
=
l
r
(
−
i
2
+
(
l
+
r
)
i
+
(
−
l
r
−
l
+
r
+
1
)
)
v
i
=
−
∑
i
=
l
r
i
2
⋅
v
i
+
(
l
+
r
)
∑
i
=
l
r
i
⋅
v
i
+
(
−
l
r
−
l
+
r
+
1
)
∑
i
=
l
r
v
i
\begin{aligned} &\quad\sum_{i=l}^r (i-l+1)(r-i+1)v_i \\ &=\sum_{i=l}^r(ri-i^2+i-lr+li-l+r-i+1)v_i \\ &=\sum_{i=l}^r(-i^2+(l+r)i+(-lr-l+r+1))v_i \\ &=-\sum_{i=l}^r i^2\cdot v_i+(l+r)\sum_{i=l}^r i\cdot v_i+(-lr-l+r+1)\sum_{i=l}^r v_i \end{aligned}
i=l∑r(i−l+1)(r−i+1)vi=i=l∑r(ri−i2+i−lr+li−l+r−i+1)vi=i=l∑r(−i2+(l+r)i+(−lr−l+r+1))vi=−i=l∑ri2⋅vi+(l+r)i=l∑ri⋅vi+(−lr−l+r+1)i=l∑rvi
然后算到这里就很明显了,我们需要维护的是区间的
∑
i
2
⋅
v
i
,
∑
i
⋅
v
i
\sum i^2\cdot v_i,\sum i\cdot v_i
∑i2⋅vi,∑i⋅vi和
∑
v
i
\sum v_i
∑vi
然后维护这几个玩意也不难,就不说了
还有一种做法在这里
T4
[NOI2007]项链工厂
一个一个操作来说吧,先不考虑旋转的情况
显然是一个线段树维护老司机树也行,维护总段数,左颜色,有颜色
-
s
w
a
p
swap
swap
先查询出 x , y x,y x,y的颜色,然后再交换涂色,线段树单点查询单点修改 -
p
a
i
n
t
paint
paint
线段树区间覆盖 -
c
o
u
n
t
count
count
查询 [ 1 , n ] [1,n] [1,n],然后如果 l e f t c o l o r = r i g h t c o l o r ∨ s e g m e n t > 1 leftcolor=rightcolor \lor segment>1 leftcolor=rightcolor∨segment>1,就答案 − 1 -1 −1就好了 -
c
o
u
n
t
s
e
g
m
e
n
t
countsegment
countsegment
区间查询
然后考虑如果有旋转情况怎么办
-
r
o
t
a
t
e
rotate
rotate
开一个变量记录一下转了多少就好了 -
f
l
i
p
flip
flip
开一个变量记录有没有反转就好了
但是!!!
r
o
t
a
t
e
+
f
l
i
p
rotate+flip
rotate+flip就变得极其恶心,细节不少
我们开一个变量
r
o
t
rot
rot记录旋转的量,
r
e
v
rev
rev表示有没有被反转过
当
r
e
v
=
0
rev=0
rev=0的时候,即没有被反转过,那么就只需要考虑
r
o
t
rot
rot,那么位置
i
i
i对应的就是
i
−
r
o
t
i-rot
i−rot(当然要取模之类的)
当
r
e
v
=
1
rev=1
rev=1的时候,我们应该先反转,然后再按上面
r
e
v
=
0
rev=0
rev=0的时候进行求值就可以了
但是!!!这里还有一个问题,就是如果 f l i p flip flip了之后,顺时针的方向也变了,也就是说,在 r e v = 1 rev=1 rev=1的时候,我们还需要把 l , r l,r l,r两个位置交换一下,然后再进行 p a i n t paint paint或者 c o u n t s e g m e n t countsegment countsegment
总结:
线段树很多时候并不处于主导作用,比如T1,T2,只是用来优化时间复杂度的嗯
我觉得各个题的实现的障碍在于
- T1:确定线段树维护什么量,还有
[
l
a
s
t
2
+
1
,
l
a
s
t
]
[last^2+1,last]
[last2+1,last]要减的问题
虽然过不了样例 - T2:想到 01 01 01做法并从 01 01 01做法推广到一般也就是二分
- T3:推式子能力或者对于情况的逐步讨论
- T4:思路不难,但是代码实现细节较多