Atcoder Library 配置入門

Shunpower發表於2024-10-20

配置

首先,你需要在這個 blog 裡面下載 Atcoder Library 的壓縮包。可以發現裡面有三堆東西,一個 python 程式,兩種語言的 document,還有一個庫資料夾。

把庫資料夾直接拖到你的編譯器庫檔案相同目錄下。Mingw 的路徑應該都是 \lib\gcc\x86_64-w64-mingw32\8.1.0\include\c++,如果不是也容易自己尋找一下。

需要使用時直接引用庫 #include <atcoder/all>

約定:存在宏定義 ll 表示 long longull 表示 unsigned long longuint 表示 unsigned int

內容

只介紹一部分比較有用的。

static_modint <mod> 型別

型別宣告形如 static_modint <mod>,其中 \(mod\) 是一個 \([1,2\times 10^9+1000]\) 的整數。還有 modint998244353modint1000000007 兩個自帶型別(前者表示 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)

其中 Tint, 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 的樹狀陣列。其中 Tint, 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]\) 的區間和。注意如果 Tint, 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() 就足夠了。

相關文章