Atcoder Library 配置入门
配置
首先,你需要在这个 blog 里面下载 Atcoder Library 的压缩包。可以发现里面有三堆东西,一个 python 程序,两种语言的 document,还有一个库文件夹。
把库文件夹直接拖到你的编译器库文件相同目录下。Mingw 的路径应该都是 \lib\gcc\x86_64-w64-mingw32\8.1.0\include\c++
,如果不是也容易自己寻找一下。或者定义环境变量 CPLUS_INCLUDE_PATH
,然后把 ac-library
文件夹的路径粘进去。
需要使用时直接引用库 #include <atcoder/all>
,并 using namespace atcoder;
。
约定:存在宏定义 ll
表示 long long
,ull
表示 unsigned long long
,uint
表示 unsigned int
。
内容
只介绍一部分比较有用的。
static_modint <mod>
类型
该类型的声明形如 static_modint <mod> x;
,其中 \(mod\) 是一个 \([1,2\times 10^9+1000]\) 的整数。还有 modint998244353
和 modint1000000007
两个自带类型(前者表示 static_modint <998244353>
,后者同理)。
ACL 为其重载了四则运算(除法需要保证有逆元)和判断符号,包括 --
,++
,+=
等运算符。
需要注意任何与 static_modint <mod>
类型的运算都会将常数强转为此类型导致取模过多造成效率下降。你可以使用 static_modint <mod>::raw(int x)
直接返回 \(x\) 而不经取模,需要保证 \(0\le x<mod\),否则行为未定义。
你可以使用 int x.val()
读取一个 static_modint <mod>
类型变量 \(x\) 的值用于其他 int
操作(例如输入输出)。
你可以使用 static_modint <mod> x.pow(ll n)
得到 \(x^n\)。
你可以使用 static_modint <mod> x.inv()
得到 \(x\) 的逆元。
dynamic_modint
类型
该类型的声明形如 dynamic_modint <mod> x;
,其中 \(mod\) 是一个 \([1,2\times 10^9+1000]\) 的整数。也可以简写为 modint x;
,它表示 dynamic_modint <-1> x;
,注意此时必须重新设置模数,否则行为未定义。
使用 void dynamic_modint <mod>::set_mod(int m)
将某种类型的 \(mod\) 修改为 \(m\),其中 \(m\) 是一个 \([1,2\times 10^9+1000]\) 的整数。你还可以使用 int dynamic_modint <mod>::mod()
来获取该类型此时的模数但我不知道有什么用。
其他使用方式同 static_modint
。注意该类型不能用于 ACL 内置的 NTT 等。
\((+,\times)\) 卷积
计算 \(c_i=\sum\limits_{j=0}^ia_jb_{i-j}\)。返回一个大小为 \(|a|+|b|-1\) 的 vector
表示 \(c\),第 \(i\) 项表示 \(c_i\)。
包含两个函数:
vector <T> convolution<mod>(vector <T> a, vector <T> b)
vector <ll> convolution_ll(vector <ll> a, vector <ll> b)
其中 T
是 int, uint, ll, ull
其中之一或者 ACL 的 static_modint
类型。
前者基于 NTT 实现,所以你需要保证 \(mod\) 是一个满足存在 \(c\) 使得 \(2^c|mod-1\) 且 \(|a|+|b|-1\le 2^c\) 的质数。如果不填入 <mod>
,则默认模数 \(998244353\)。时间复杂度同 NTT,是 \(\mathcal O(n\log n+\log p)\)。
后者需要保证卷出来的结果都在 long long
范围内,且 \(|a|+|b|-1\le 2^{24}\)。时间复杂度 \(\mathcal O(n\log n)\)。
dsu
本质是一个结构体。你可以使用 dsu d(int n)
声明并初始化 \(fa_i=i\) 一个大小为 \(n\) 名为 d
的并查集。注意点的编号是从 \(0\) 开始的。
int d.merge(int a, int b)
:将 \((a,b)\) 连接在一起,并返回连通块的代表元。bool d.same(int a, int b)
:返回 \((a,b)\) 是否在同一连通块内。int d.leader(int a)
:返回 \(a\) 所在连通块的代表元。int d.size(int a)
:返回 \(a\) 所在连通块的大小。vector<vector<int>> d.groups()
:返回所有连通块包含的点的集合的集合。
Fenwick Tree(树状数组)
本质是一个结构体。你可以使用 fenwick_tree<T> fw(int n)
声明并初始化为全 \(0\) 一个大小为 \(n\) 类型为 T
名为 fw
的树状数组。其中 T
是 int, uint, ll, ull
其中之一或者 ACL 的 static_modint
类型。注意下标的编号是从 \(0\) 开始的。
void fw.add(int p, T x)
:单点 \(p\) 加上 \(x\)。T fw.sum(int l, int r)
:查询 \([l,r]\) 的区间和。注意如果T
是int, uint, ll, ull
其中之一,如果答案超出范围,则会自然溢出(对 \(2^\text{bit}\) 取模)。
(带懒标记的)线段树
我愿称之为最吊。
考虑线段树的数学意义,其实就是半群单点修改和区间求积。所以我们要重载半群信息和乘法。你需要确保自己的半群乘法具有结合律。事实上,对于合理的半群信息维护,你总能将形式写成矩阵乘法,所以总是具有结合律。
你有两种方式 \(\mathcal O(n)\) 建立一棵包含懒标记的线段树,同样注意下标的编号是从 \(0\) 开始的:
lazy_segtree<S, op, e, F, mapping, composition, id> seg(int n);
lazy_segtree<S, op, e, F, mapping, composition, id> seg(vector<S> v);
前半部分是相同的:
S
是一个结构体,包含了所有你的标记和答案所需要维护的半群信息。op
是一个函数S op(S x, S y)
,表示将信息 \(x\) 左乘信息 \(y\) 合并在一起得到的信息(即 \(x\cdot y\))。e
是一个函数S e()
,表示信息的左乘单位元,也就是满足 \(e\cdot x=x\) 的 \(e\)。F
是一个结构体,包含了你的标记信息。mapping
是一个函数S mapping(F x, S y)
,表示将标记 \(x\) 左乘信息 \(y\) 合并在一起得到的信息。composition
是一个函数F composition(F x, F y)
,表示将标记 \(x\) 左乘标记 \(y\),也就是将标记 \(x\) 合并到 \(y\) 得到的标记(即 \(x\circ y\))。id
是一个函数F id()
,表示标记的左乘单位元,也就是合并到其他标记上不会影响其他标记的标记。也就是满足 \(\text{id}\circ x=x\) 的 \(\text{id}\)。
后半部分,对于前者表示生成一棵维护 \([0,n-1]\) 的线段树,目前每个元素为信息的单位元 \(e\);对于后者表示生成一棵维护 \([0,siz(v)-1]\) 的线段树,目前每个元素为 v
中的对应元素。
然后你可以使用以下函数:
void seg.set(int p, S x)
:单点修改 \(p\) 上的信息为 \(x\)。S seg.get(int p)
:单点查询 \(p\) 上的信息。S seg.prod(int l, int r)
:区间查询左闭右开区间 \([l,r)\) 的区间半群信息。对于 \(l=r\) 返回单位元。对于 \(l>r\) 行为未定义。S seg.all_prod()
:查询 \([0,n)\) 的整个半群信息,时间复杂度 \(\mathcal O(1)\)。void seg.apply(int p, F f)
:单点修改,将标记 \(f\) 合并到 \(p\) 上的信息。void seg.apply(int l, int r, F f)
:区间修改,将标记 \(f\) 合并到 \([l,r)\) 的所有信息。
还有一些线段树上二分操作。
你需要定义一个确定性的函数 bool g(S x)
,需要满足其你所需的二分单调性。特别地,你需要使得 g(e())
的返回值为 true
。
int seg.max_right<g>(int l)
函数:返回右侧第一个 \(r\) 使得 \([l,r)\) 的区间信息的 \(g\) 值是true
且 \([l,r]\) 的区间信息的 \(g\) 值是false
。特别地,如果所有值均为true
则返回 \(n\);如果所有值均为false
则返回 \(l\)。int seg.min_left<g>(int r)
函数:返回左侧第一个 \(l\) 使得 \([l-1,r)\) 的区间信息的 \(g\) 值是false
且 \([l,r)\) 的区间信息的 \(g\) 值是true
。特别地,如果所有值均为true
则返回 \(0\);如果所有值均为false
则返回 \(r\)。
以上操作无特殊说明时间复杂度均为 \(\mathcal O(\log n)\)。当然还要乘上标记或信息合并的复杂度 \(\mathcal O(T)\)。
对于不带懒标记的线段树,主体部分只实现 S, op(), e()
就足够了。
数学库(类欧、逆元、快速幂、CRT)
ll pow_mod(ll x, ll n, int m)
:返回 \(x^n\bmod m\)。ll inv_mod(ll x, ll m)
:返回 \(x\) 在模 \(m\) 意义下的逆元。ll floor_sum(ll n, ll m, ll a, ll b)
:使用类欧,返回 \(\sum\limits_{i=0}^{n-1}\left\lfloor\frac{ai+b}{m}\right\rfloor\),复杂度 \(\mathcal O(\log (n+m+a+b))\)。你需要保证 \(a,b<m\)。pair<ll, ll> crt(vector<ll> r, vector<ll> m)
:使用 ExCRT,返回方程组 \(x\equiv r_i\pmod {m_i}\) 的解。若无解,返回 \((0,0)\),否则返回 \((y,z)\) 表示解为 \(x\equiv y\pmod z\),其中 \(z=\operatorname{lcm}(m_i)\)。若方程数量为 \(0\) 则返回 \((0,1)\)。你需要保证 \(\operatorname{lcm}(m_i)\) 在long long
范围内。