配置
首先,你需要在這個 blog 裡面下載 Atcoder Library 的壓縮包。可以發現裡面有三堆東西,一個 python 程式,兩種語言的 document,還有一個庫資料夾。
把庫資料夾直接拖到你的編譯器庫檔案相同目錄下。Mingw 的路徑應該都是 \lib\gcc\x86_64-w64-mingw32\8.1.0\include\c++
,如果不是也容易自己尋找一下。
需要使用時直接引用庫 #include <atcoder/all>
。
約定:存在宏定義 ll
表示 long long
,ull
表示 unsigned long long
,uint
表示 unsigned int
。
內容
只介紹一部分比較有用的。
static_modint <mod>
型別
型別宣告形如 static_modint <mod>
,其中 \(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\) 的逆元。
\((+,\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()
就足夠了。