min 25 模板使用說明 前幾個月(WF 之前)忘記為什麼就想去學 min25 篩,然後就把他模版化放進 codebook 裡了。我是看 https://oi-wiki.org/math/number-theory/min-25/ 學的,不過本文不會提到太多細節。
先貼上 code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
template < typename U , typename V > struct min25 {
lld n ; int sq ;
vector < U > Ss , Sl , Spre ; vector < V > Rs , Rl ;
Sieve sv ; vector < lld > quo ;
U & S ( lld d ) { return d < sq ? Ss [ d ] : Sl [ n / d ]; }
V & R ( lld d ) { return d < sq ? Rs [ d ] : Rl [ n / d ]; }
min25 ( lld n_ ) : n ( n_ ), sq (( int ) sqrt ( n ) + 1 ),
Ss ( sq ), Sl ( sq ), Spre ( sq ), Rs ( sq ), Rl ( sq ), sv ( sq ) {
for ( lld i = 1 , Q ; i <= n ; i = n / Q + 1 )
quo . push_back ( Q = n / i );
}
U F_prime ( auto && f , auto && F ) {
for ( lld p : sv . primes ) Spre [ p ] = f ( p );
for ( int i = 1 ; i < sq ; i ++ ) Spre [ i ] += Spre [ i - 1 ];
for ( lld i : quo ) S ( i ) = F ( i ) - F ( 1 );
for ( lld p : sv . primes )
for ( lld i : quo ) {
if ( p * p > i ) break ;
S ( i ) -= f ( p ) * ( S ( i / p ) - Spre [ p - 1 ]);
}
return S ( n );
} // F_prime: \sum _ {p is prime, p <= n} f(p)
V F_comp ( auto && g , auto && h ) {
for ( lld i : quo ) R ( i ) = h ( S ( i ));
for ( lld p : sv . primes | views :: reverse )
for ( lld i : quo ) {
if ( p * p > i ) break ;
lld prod = p ;
for ( int c = 1 ; prod * p <= i ; ++ c , prod *= p ) {
R ( i ) += g ( p , c ) * ( R ( i / prod ) - h ( Spre [ p ]));
R ( i ) += g ( p , c + 1 );
}
}
return R ( n );
} // F_comp: \sum _ {2 <= i <= n} g(i)
}; // O(n^{3/4} / log n)
如果去看我們 codebook 的連結 會看到還有一串中文的註解,以下大概就是仔細的講那一串中文的註解然後隨機補充一些東西。
Description 這個模板要解決的問題簡單來說就是有一個積性函數 g g g ,計算 ∑ i ≤ n g ( i ) \sum _ {i \leq n} g(i) ∑ i ≤ n g ( i ) 。因為是積性函數,所以只要給定質數冪的值,即 g ( p c ) g(p^c) g ( p c ) ,就可以對所有正整數定義 g g g 的值。然而為了快速計算 g g g 還必須滿足一些條件,在學 min25 篩時常常會看到「質數點 p p p 必須是一個低階多項式」,不過其實可以更一般化。使用這個模板時必須要構造兩個 type U , V U, V U , V 以及函數 f , h f, h f , h 滿足以下條件:
U , V U, V U , V 都是環,即在上面可以定義加法、乘法,加法可交換且有反元素(可以做減法),乘法對加法有分配律。要求 f : N → U f: \mathbb{N} \to U f : N → U 是完全積性,即 ∀ a , b ∈ N , f ( a b ) = f ( a ) f ( b ) \forall a, b \in\mathbb{N}, f(ab) = f(a)f(b) ∀ a , b ∈ N , f ( ab ) = f ( a ) f ( b ) 。 f f f 的前綴和 F ( i ) = ∑ j ≤ i f ( i ) F(i) = \sum _ {j \leq i} f(i) F ( i ) = ∑ j ≤ i f ( i ) 可以快速求值。對於質數 p p p ,g ( p ) = h ( f ( p ) ) g(p) = h(f(p)) g ( p ) = h ( f ( p )) 。 h : U → V h: U \to V h : U → V 是一個加法上的同態,即 h ( u 1 + u 2 ) = h ( u 1 ) + h ( u 2 ) h(u_1 + u_2) = h(u_1) + h(u_2) h ( u 1 + u 2 ) = h ( u 1 ) + h ( u 2 ) 。給定 p , c p, c p , c 之後,g ( p c ) g(p^c) g ( p c ) 可以快速求值。 呼叫 F_prime 會對所有 x ∈ D n = ⌊ n i ⌋ ∣ i = 1 , 2 , … , n x \in D_n = \\{\lfloor \frac{n}{i} \rfloor | i=1,2,\dots,n \\} x ∈ D n = ⌊ i n ⌋ ∣ i = 1 , 2 , … , n 計算 S ( x ) = ∑ p is prime , p ≤ x f ( p ) S(x) = \sum _ {p \text{ is prime}, p \leq x} f(p) S ( x ) = ∑ p is prime , p ≤ x f ( p ) 。請注意 F ( x ) = ∑ i ≤ x f ( i ) F(x) = \sum _ {i \leq x} f(i) F ( x ) = ∑ i ≤ x f ( i ) ,和 S S S 不同。 呼叫 F_comp 會對所有 x ∈ D n x \in D_n x ∈ D n 計算 R ( x ) = ∑ 2 ≤ i ≤ x g ( i ) R(x) = \sum _ {2 \leq i \leq x} g(i) R ( x ) = ∑ 2 ≤ i ≤ x g ( i ) 。函數回傳的值就是 R ( n ) R(n) R ( n ) ,也就是最一開始求的值。注意這個值不會把 g ( 1 ) g(1) g ( 1 ) 算進去。
以下舉幾個例題說明。
sum of totient function 大家很熟悉的積性函數 φ ( n ) \varphi(n) φ ( n ) 。對於質數冪,φ ( p c ) = p c − 1 ( p − 1 ) \varphi(p^c) = p^{c-1}(p-1) φ ( p c ) = p c − 1 ( p − 1 ) ,特別的,對於質數 p p p 有 φ ( p ) = p − 1 \varphi(p) = p - 1 φ ( p ) = p − 1 。這滿足所謂低階多項式的條件。讓我們構造一下 f f f
{ f ( n ) = ( 1 , n ) h ( ( s 0 , s 1 ) ) = s 1 − s 0
\begin{cases}
f(n) &= (1, n) \\
h((s_0, s_1)) &= s_1 - s_0
\end{cases}
{ f ( n ) h (( s 0 , s 1 )) = ( 1 , n ) = s 1 − s 0 其中,U = Z 2 U = \mathbb{Z}^2 U = Z 2 上的加法跟乘法分別都定義成逐項加跟逐項乘,即 ( a , b ) + ( c , d ) = ( a + c , b + d ) (a, b) + (c, d) = (a + c, b + d) ( a , b ) + ( c , d ) = ( a + c , b + d ) 跟 ( a , b ) ⋅ ( c , d ) = ( a c , b d ) (a, b) \cdot (c, d) = (ac, bd) ( a , b ) ⋅ ( c , d ) = ( a c , b d ) 。可以驗證對於質數 p p p ,h ( f ( p ) ) = h ( ( 1 , p ) ) = p − 1 = φ ( p ) h(f(p)) = h((1, p)) = p - 1 = \varphi(p) h ( f ( p )) = h (( 1 , p )) = p − 1 = φ ( p ) 。
在 g ( p ) = ∑ a i p i g(p) = \sum a_i p^i g ( p ) = ∑ a i p i 的時候,U U U 的構造基本上就是一個 tuple 維護 n 0 , n 1 , n 2 , … n^0, n^1, n^2, \dots n 0 , n 1 , n 2 , … ,而 id k : n ↦ n k \text{id}_k: n\mapsto n^k id k : n ↦ n k 正是完全積性並且前綴和很好算的函數。h h h 則就是把這些冪次用 a i a_i a i 線性組合得到 g g g 。
sum of multiplicative function 和 sum of totient function 幾乎一樣,只是 h h h 乘的系數稍微改一下。稍微放一點片段
1
2
3
4
5
6
7
8
9
10
11
12
min25 < Pair , Mint > m ( N );
m . F_prime ([]( Mint p ) -> Pair {
return { 1 , p };
}, []( Mint n ) -> Pair {
return { n , n * ( n + 1 ) / 2 };
});
Mint ans = 1 + m . F_comp ([ & ]( Mint p , Mint e ) -> Mint {
return a * e + b * p ;
}, [ & ]( Pair v ) -> Mint {
return a * v [ 0 ] + b * v [ 1 ];
});
其中 Pair 是一個定義了加法乘法減法等等的 struct,也可以直接用 std::valarray 代替,但可能會跑比較慢。
可以看 這個 submission 或是 另一個 submission 。
counting primes 可以只呼叫 F_prime 得到質數的個數,此時 f ( n ) = 1 f(n) = 1 f ( n ) = 1 以及 F ( n ) = n F(n) = n F ( n ) = n 。 可見 https://judge.yosupo.jp/submission/255568 。
LOJ #6053. 简单的函数 https://loj.ac/p/6053
本題的 g ( p c ) = p ⊕ c g(p^c) = p \oplus c g ( p c ) = p ⊕ c 。因為質數除了 2 2 2 以外都是奇數,所以 g ( p ) = p − 1 g(p) = p - 1 g ( p ) = p − 1 ,除了 g ( 2 ) = 3 g(2) = 3 g ( 2 ) = 3 。其中一個構造如下:
{ f ( n ) = ( 1 , n , [ n is odd ] ) h ( ( s 0 , s 1 , s p a r i t y ) ) = s 1 + s 0 − 2 s p a r i t y
\begin{cases}
f(n) &= (1, n, [n \text{ is odd}]) \\
h((s_0, s_1, s_{parity})) &= s_1 + s_0 - 2 s_{parity}
\end{cases}
{ f ( n ) h (( s 0 , s 1 , s p a r i t y )) = ( 1 , n , [ n is odd ]) = s 1 + s 0 − 2 s p a r i t y 注意到 n ↦ [ n is odd ] n \mapsto [n \text{ is odd}] n ↦ [ n is odd ] 也是一個完全積性函數,並且其前綴和很好計算:介在 [ 1 , n ] [1, n] [ 1 , n ] 的奇數數量正好就是 ⌈ n 2 ⌉ \lceil \frac{n}{2} \rceil ⌈ 2 n ⌉ 。這裡 U = Z 3 U = \mathbb{Z}^3 U = Z 3 並且類似的定義加法乘法。
QOJ # 9867. Flowers 這是在 3rd ucup kunming 遇到的題目。原題有一些包裝,但總之就是要計算 ∏ i ≤ n ω ( i ) \prod _ {i \leq n} \omega(i) ∏ i ≤ n ω ( i ) ,其中 ω ( i ) \omega(i) ω ( i ) 是 i i i 有幾種相異的質因數。因為 n ≤ 10 11 n\leq 10^{11} n ≤ 1 0 11 ,用 python 打一下可以知道 ω ( i ) ≤ 10 \omega(i) \leq 10 ω ( i ) ≤ 10 。
因為相異質因數個數很小,可以打算對於所有 k = 1 , … , 10 k = 1,\dots,10 k = 1 , … , 10 計算有幾個 i i i 使得 ω ( i ) = k \omega(i) = k ω ( i ) = k 之後再快速冪計算原題要求的答案。為了數有幾個相異質因數,構造 V = Z [ x ] V = \mathbb{Z}[x] V = Z [ x ] 為多項式,而 g ( p c ) = x g(p^c) = x g ( p c ) = x ,這樣當 i i i 有 k k k 個相異質因數,g ( i ) g(i) g ( i ) 就會等於 x k x^k x k ,而 ∑ 2 ≤ i ≤ n g ( i ) \sum _ {2\leq i \leq n} g(i) ∑ 2 ≤ i ≤ n g ( i ) 就是我們想要統計的東西。
接著想要構造一個前綴和很好算的 f f f 使得在質數點 f f f 和 g g g 可以通過 h h h 轉換。從 ∑ p ∈ P ≤ n g ( p ) = π ( n ) x \sum _ {p\in\mathbb{P} _ {\leq n}} g(p) = \pi(n) x ∑ p ∈ P ≤ n g ( p ) = π ( n ) x 可以看出(hint: ∑ p ∈ P ≤ n g ( p ) = ∑ p ∈ P ≤ n h ( f ( p ) ) = h ( ∑ p ∈ P ≤ n f ( p ) ) \sum _ {p\in\mathbb{P} _ {\leq n}} g(p) = \sum _ {p\in\mathbb{P} _ {\leq n}} h(f(p)) = h\left( \sum _ {p\in\mathbb{P} _ {\leq n}} f(p) \right) ∑ p ∈ P ≤ n g ( p ) = ∑ p ∈ P ≤ n h ( f ( p )) = h ( ∑ p ∈ P ≤ n f ( p ) ) ,不限於此題)在 F_prime 階段要計算的大概就會是質數數量,不妨構造 f ( n ) = 1 f(n) = 1 f ( n ) = 1 以及 h ( s ) = s x h(s) = sx h ( s ) = s x 。
因為 ω ( i ) ≤ 10 \omega(i) \leq 10 ω ( i ) ≤ 10 ,所以本題的多項式 degree 也小於等於 10 10 10 ,因此我的實做是直接暴力 10 × 10 10\times 10 10 × 10 寫乘法,但實際上也可以用 NTT,或甚至在一開始任選 11 個數字,然後在 min25 篩計算他們帶入多項式的點值的和,最後再用拉格朗日插值或高斯消去等方法得出答案的多項式,不過嫌麻煩就沒有把這個 10 壓掉也是過了。
另外一點我想提的是 F_comp 有另外一個寫法是用 top-down dfs 甚至不記憶化 的寫法去計算,應該是 OI-wiki 上的所謂 O ( n 1 − ε ) \mathcal{O}(n^{1-\varepsilon}) O ( n 1 − ε ) 的作法,大概跟 F_comp 想做的事情差不多。把 F_comp 滾動掉的那個維度 explicit 的寫出來就會變成下面這樣:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
V F_comp_dfs ( auto && g , auto && h ) {
auto dfs = [ & ]( auto && self , size_t k , lld x ) -> V {
V ans = h ( S ( x ));
for ( size_t i = k ; i < sv . primes . size (); i ++ ) {
const lld p = sv . primes [ i ];
if ( p * p > x ) break ;
lld prod = p ;
for ( int c = 1 ; prod * p <= x ; ++ c , prod *= p ) {
ans += g ( p , c ) * ( self ( self , i + 1 , x / prod ) - h ( Spre [ p ]));
ans += g ( p , c + 1 );
}
}
return ans ;
};
return dfs ( dfs , 0 , n );
}
節錄自 這個 submission 。本文的這個 O ( n 3 4 log − 1 n ) \mathcal{O}(n^{\frac{3}{4}} \log ^{-1} n) O ( n 4 3 log − 1 n ) 複雜度好像會在中國被分類成洲閣篩?而 dfs 的寫法才被叫做 min25 篩。複雜度雖然是 O ( n 1 − ε ) \mathcal{O}(n^{1-\varepsilon}) O ( n 1 − ε ) 但在競程範圍內算是蠻快的,如果 F_comp 特別慢的題目,例如 QOJ 那題,會有顯著加速。
Time Complexity O ( n 3 4 / log n ) \mathcal{O}(n^{\frac{3}{4}} / \log n) O ( n 4 3 / log n ) 。從程式碼裡面就可以看出來(注意到 quo 是從大到小枚舉) F_prime 的複雜度是
∑ p is prime , p ≤ n ∑ x ∈ D n [ p 2 ≤ x ] = ∑ x ∈ D n π ( x ) ≤ ∑ i 2 ≤ n π ( i ) + ∑ i 2 ≤ n π ( n i ) ≈ ∑ i 2 ≤ n i ln i + ∑ i 2 ≤ n n i ln n i
\sum _ {p \text{ is prime}, p \leq \sqrt{n}} \sum _ {x \in D_n} [p^2 \leq x]
= \sum _ {x\in D_n} \pi(\sqrt{x})
\leq \sum _ {i^2 \leq n} \pi(\sqrt{i}) + \sum _ {i^2 \leq n} \pi(\sqrt{\frac{n}{i}})
\approx \sum _ {i^2 \leq n} \frac{\sqrt{i}}{\ln{\sqrt{i}}} + \sum _ {i^2 \leq n} \frac{\sqrt{\frac{n}{i}}}{\ln{\sqrt{\frac{n}{i}}}}
p is prime , p ≤ n ∑ x ∈ D n ∑ [ p 2 ≤ x ] = x ∈ D n ∑ π ( x ) ≤ i 2 ≤ n ∑ π ( i ) + i 2 ≤ n ∑ π ( i n ) ≈ i 2 ≤ n ∑ ln i i + i 2 ≤ n ∑ ln i n i n 後續就懶得證了。應該可以在隨意的 n c n^c n c 切一刀,一半把分母當成 1 1 1 積分也只會是 O ( n 3 4 − ε ) O(n^{\frac{3}{4}-\varepsilon}) O ( n 4 3 − ε ) ,另一半則是可以把 ln ( ⋯ ) \ln(\cdots) ln ( ⋯ ) 當成 log ( n ) \log(n) log ( n ) 的常數倍。F_comp 的複雜度應該也可以證明是一樣的。
空間跟 D n D_n D n 集合的大小一樣是 O ( n ) \mathcal{O}(\sqrt{n}) O ( n ) 。
Caveats 這份程式碼並沒有直接要求 U , V U, V U , V 的加法單位元和乘法單位元(0 , 1 0, 1 0 , 1 ),但都預設空的 vector 會是 0 0 0 ,並且因為 f , g f, g f , g 是積性的,定義上應該要滿足 f ( 1 ) = 1 U , g ( 1 ) = 1 V f(1) = 1_U, g(1) = 1_V f ( 1 ) = 1 U , g ( 1 ) = 1 V 。
另外,完全積性函數這個條件,我懷疑其實也是可以拿掉的。在第一階段(F_prime)我們會把包含小質數的數字全部扣掉,這相當於是跟 f − 1 f^{-1} f − 1 做捲積,而當 f f f 是完全積性函數,inverse 會非常好算:f p − 1 ( x ) = 1 − f ( p ) x f_p^{-1}(x) = 1 - f(p)x f p − 1 ( x ) = 1 − f ( p ) x (見貝爾級數 ),若 f f f 不是完全積性的,inverse 要花點力氣算。不過,我們仍然會需要構造一個前綴和很好算的積性函數 f f f ,這是這個演算法的大前提。把完全積性的條件改掉之後可能常數會差一點,而且當初寫的時候沒有想這麼多。
這份程式碼如果 f , g , h , F f,g,h,F f , g , h , F 都是 O ( 1 ) \mathcal{O}(1) O ( 1 ) 的話,大概可以跑 10 10 10^{10} 1 0 10 到 10 11 10^{11} 1 0 11 。因為據說有 O ~ ( N ) \tilde{\mathcal{O}}(\sqrt{N}) O ~ ( N ) 的演算法了所以就去翻了一堆文章,還是連同樣條件下的 O ( n 2 3 log − 1 n ) \mathcal{O}(n^{\frac{2}{3}} \log^{-1}n) O ( n 3 2 log − 1 n ) 的版本也寫不出來。去翻 library checker 的各種 top coder 好像也都是 O ~ ( n 2 3 ) \tilde{\mathcal{O}}(n^{\frac{2}{3}}) O ~ ( n 3 2 ) 的複雜度的。只有再次學會了杜教篩,寫了一個帶 template 的模板過了 sum_of_totient_function ,但跟本文的模板比也只是 192ms 跟 397ms 的差距而已。