log 理论
为何你的代码总是常数大?快来学习 \(\log\) 理论,看看究竟是哪里无形之中产生了大常数。
帮你理解各种 \(O(\log)\) 常数。
以下算法大体按照常数从小到大排序。
小:跑不满 \(\log\)。
中:大概会跑到 \(\log\)
大:会多于 \(\log\)
巨:约等于 \(\log^2\)
数据结构
小 \(\log\)
- 树剖/dsu on tree
- 树状数组
- 李超线段树
中 \(\log\)
- 普通 zkw 线段树
大 \(\log\)
- 普通线段树
超级巨 \(\log\)
- 主席树/其他动态开点线段树
- map/set
- 普通平衡树(FHQ)
注:以上线段树均指 pushup
和 pushdown
都很简短的线段树。
附:线段树各种操作的常数
单点操作/查询:中 \(\log\)
区间操作/查询:大 \(\log\)
全局二分:大 \(\log\)
区间二分:巨 \(\log\)(born_to_sun 版本)
线段树合并:巨 \(\log\)
可持久化线段树合并:超级巨 \(\log\)
更精细的常数,参见 数据结构的精细复杂度
算法
小 \(\log\)
- 启发式分裂
- 调和级数
- 启发式合并
- Boruvka
中 \(\log\)
- \(\operatorname{sort}\)
- 堆\(^{[1]}\)
- lower_bound
- 手写二分
- 点分治/点分树
- cdq 分治
- st 表预处理
大 \(\log\)
- 边分治
- SA
注:\([1]\),如果堆一直 push
,很快,但是 pop
就很慢,原因不详。堆的空间常数可以粗略认为是 vector,因为堆的底层时限是 vector。
STL
-
vector
一个或很少个数的
vector.push_back
,很快。多个
vector.push_back
,访问较为连续,一般。多个
vector.push_back
,随机访问,很慢。 -
set
和 vector 类似,但是因为复杂度带 \(\log\),当插入元素个数一定时,常数关于容器个数呈单峰函数。
后记
部分算法因为核心思想不同,无法相互取代,常数大小很难比较。比如让我比较边分治和 SA 的常数就很困难,但 sort,堆,lower_bound 在某些条件下可以相互取代,因此常数能够比较。