这是我个人的算法竞赛模板库。
大多数代码可以在 (gcc, C++11) , (clang, C++11) 和 (msvc, C++14) 环境下编译运行。如有特殊情况会注明。
由于 C++17 的 std::gcd/std::lcm 以及 C++20 的位运算使用较多,所以本模板库内置了 std::bit.h 和 std_gcd_lcm.h 两个头文件。语言版本较低的使用者在导入这两个头文件之后,就可以使用 std::gcd/std::lcm/std::popcount 等。
-
速度极快
RMQ
问题是算法竞赛中常见的问题。如果带修,那么需要使用线段树来维护,以$O(\log n)$ 的时间复杂度进行修改和查询。在
atcoder
运行benchmark
显示,本模板库的OY::MonoMaxZkw<uint32_t>
在n = 1e6
的情况下,一秒钟可以做3.3e7
次区间最值查询。惊人的速度。 -
使用方便
FOR
宏定义?i64
缩写?编程老手都认识?No
!本模板库中,不会使用这些花里胡哨的缩写,也不会假定使用者是老手。本模板,让任何码风的人都不会感到不适应。模板高度封装,使用的时候可以当成黑盒,而不需要对模板内部做手脚。每份代码都有对应的文档,提供使用范例;
TEST
文件夹里,提供了本地运行代码,以及在若干oj
题目上提交时的代码。
-
大值域的线性空间线段树(
CompressedTree
维护0~1e18
范围的权值线段树,空间复杂度正比于操作次数) -
动态大小的
bitset
(DynamicBitset
效率与静态大小的bitset
持平) -
可以动态维护全局半群信息的双向队列(
Deque
完爆std::deque
) -
可以维护区间加定值、区间加一次函数、区间加
k
次函数的树状数组 (KBIT
) -
当只有邻项合并操作时,严格线性时间复杂度的并查集(
LinearDSU
) -
线性时间初始化,
$O(1)$ 查询区间最值的状压RMQ
(MaskRMQ
) -
支持区间翻转、区间剪切合并,同时维护区间半群信息的平衡树(
MonoAVL
,MonoSplay
) -
查询速度极快的单点修改、区间查询线段树(
MonoZkw
) -
二维、三维以及更高维度上,维护半群信息(可以带修)的多维表/树(
MultiDimSegTree
) -
通过编写
node
以实现自定义操作的势能线段树(SegmentBeat
) -
支持区间排序,并维护区间半群信息的线段树/平衡树 (
SortFHQ
,SortSeg
) -
线性时间初始化, 平均
$O(1)$ 查询区间半群信息的根树(SqrtTree
) -
单点加修改、区间和查询速度极快,碾压树状数组的数据结构 (
WTree
) -
各种存储结构和算法执行器独立分开的图模板;
-
动态/静态模数的、32位/64位模数的、使用/不使用蒙哥马利模乘的
modint
; -
运用了蒙哥马利模乘的运行极快的质数判定(
PrimeCheck
) -
运用了蒙哥马利模乘的运行极快的因数分解(
Pollard Rho
) -
支持基于范围遍历的数论分块 (
SqrtDecomposition
) -
支持自定义个数、种类的模数的序列哈希(
SequenceHash
,SequenceMultiHash
) -
支持回滚操作的
KMP
,回文自动机; -
线性时间复杂度的后缀数组;
-
通过编写
node
以实现自定义操作的动态树(GBT
,LCT
) -
结合其他模板,快速制造出各种树套树数据结构的控制器(
LeveledBITManipulator
,LeveledZkwManipulator
,LeveledSegManipulator
) -
高度抽象的数位
dp
模板; -
线性空间同时维护最大值和最小值的堆(
MinMaxHeap
); -
支持对交换幺半群进行子矩形修改、子矩形查询的二维线段树;
-
实用的
leetcode
输入输出工具,支持网页端的同样格式的输入输出数据。
-
我的编程环境非常老旧,看到你的模板库代码花里胡哨的,能运行起来吗?
本模板库现已放宽对语言环境的要求,绝大多数模板,
gcc
和clang
在C++11
之后均可使用;MSVC
在C++14
之后均可使用(暂无C++11
测试环境)。只要你的C++
语言标准在C++11
之后,均可以使用我的模板库。 -
在
LCT
和GBT
等一些模板里,看到MAX_NODE
参数,这是什么意思?在这些模板里,通过
MAX_NODE
控制全局内存池的大小,,从这个全局内存池向不同的树对象分配结点。 -
在模板里,填写的
MAX_NODE
是否越大越好?如果是多组测试,是否每组测试重新构造一个数据结构对象就会触发$O(MAXNODE)$ 的初始化导致超时?以下回答针对你的结点类型为平凡类型的情况(无构造函数,无初始值)。
MAX_NODE
相关联的是结点内存池的大小,所以并不会出现每次构造一个数据结构对象,就导致内存池初始化的情况。MAX_NODE
并非越大越好,当MAX_NODE
过大时,编译可能会失败。只要编译能通过,那么在此范围内MAX_NODE
多大都没关系,也不会有任何的时间开销。 -
线段树只能有求和的功能吗?
本模板库非常重视模板的泛化程度。
一般来说,包括线段树在内的维护半群的数据结构,天然带有
Min
,Max
,Gcd
,Lcm
,BitAnd
,BitOr
,BitXor
以及Sum
这八种实例类型。如果有额外的需求,可以通过make_
系列来定义新的类型;或者传递自定义的Monoid
半群类型。其他的容器也往往如此。 -
线段树模板参数一大堆,填写起来老是报错?连类型名字都不能完整写出来该怎么办?
为了防止定义各种千奇百怪运算符的使用者在使用模板时,因为无法描述出模板的完整类型名称而困扰,所以特意编写了
make_
系列函数。如同std::make_pair
以及std::make_tuple
一般,只需要填写少量参数即可创建出复杂类型的模板。例如,make_SegTree
可以用来创建线段树;只要打出make_SegTree
之后跟随IDE
的智能提示进行相应的填写即可。 -
用
make_SegTree
可以创建一颗线段树;但是如果我要在std::vector
里存放十颗线段树,我还是得把类型全称写出来,可是我写不出来,怎么办?既然用
make_SegTree
可以创建出一颗线段树,那么可以用using NickName = decltype(make_SegTree<...>(...));
来捕获这棵树的类型,并给它起个别名。接下来即可用std::vector<NickName>
的方式存储十颗线段树。 -
为什么使用
StaticModInt64
,DynamicModInt64
取模结果出错?本模板库要求
gcc
或者clang
编译器的long double
具有80
个bit
的size
;可以通过std::numeric_limits<long double>::max()
检查输出是否为pow(10, 4932)
以上。如果不够,那么基于long double
进行的计算就可能因精度不足而出错。一般而言,
MSVC
编译器的long double
只有64
个bit
的size
,所以在MSVC
环境里往往不使用long double
进行计算,而是通过其他算法进行计算。所以,MSVC
编译器的long double
位数不足不会导致计算结果错误。