进行RMQ或类似问题可以采用如下思路:
一、暴力求解
通常用于对拍或实在想不出更好的方法时。最坏情况下的复杂度一般达到 O ( n ) O(n) O(n),只能通过较弱的的数据。
二、分块
通过将数据分为 ⌊ n ⌋ \lfloor {\sqrt n}\rfloor ⌊n⌋块,每块维护相应数据(和、最大值、最小值等),注意如果存在区间修改时要设置标记。时间复杂度降至 O ( n ) O(\sqrt n) O(n),在想不到如何用更好的方法(线段树、树状数组等)实现、题目时间限制不紧或做题时间比较紧时,可以尝试。
有关用分块处理的各类RMQ问题,参考这篇博客
三、线段树
线段树可以处理符合结合律的各种操作,如区间加、区间乘、区间异或等。复杂度为
O
(
l
o
g
n
)
O(log n)
O(logn)但常数较大。
如果存在区间修改操作,应当设置懒标记。对于给定的运算
⊕
\oplus
⊕,懒标记下放的方法为:
tag[son]⊕=tag[fa];
data[son]=(⊕运算对数据的影响,如加法为+=tag[fa]*child_len,
乘法为*=tag[fa])
如果有多个区间修改操作,应当人为给懒标记确定顺序。必须考虑:
- 确定为后更新的操作对应的懒标记,先更新的操作对它的影响
- 两种操作分别如何作用于数据
比如:加、乘两种操作,确定为先乘后加,那么,两种操作对数据的作用如下:
- 加法:new_data=data+length*add_tag
- 乘法:new_data=data*mul_tag
后进行的操作为加法,乘法对它的影响为:需要乘上之前乘过的值,即new_add_tag=add_tag*length
四、树状数组
树状数组是简化的线段树,比线段树常数小。适用于单点修改、区间查询的情况。如果是涉及到前缀和操作的区间修改,也可以考虑采用差分的方法间接使用。
树状数组通过将结点转化为二进制求解。如进行单点加操作:
while(i>0) {
bit[i]+=val;
i-=lowbit(i);
}
区间查询([1,i]):
while(i<=n) {
res+=bit[i];
i+=lowbit(i);
}