前置知识
莫比乌斯函数
定义
设正整数
n = p 1 a 1 p 2 a 2 … p k a k n = p_1^{a_1} p_2^{a_2} \dots p_k^{a_k} n = p 1 a 1 p 2 a 2 … p k a k
其中
p i p_i p i 是互不相同的质数,
a i ≥ 1 a_i \ge 1 a i ≥ 1 。
则莫比乌斯函数
μ ( n ) \mu(n) μ ( n ) 定义为:
μ ( n ) = { 1 若 n = 1 ( − 1 ) k 若 a 1 = a 2 = ⋯ = a k = 1 0 若 ∃ a i > 1 \mu(n) = \begin{cases}
1 & \text{若 } n = 1 \\
(-1)^k & \text{若 } a_1 = a_2 = \dots = a_k = 1 \\
0 & \text{若 } \exists \, a_i > 1
\end{cases} μ ( n ) = ⎩ ⎨ ⎧ 1 ( − 1 ) k 0 若 n = 1 若 a 1 = a 2 = ⋯ = a k = 1 若 ∃ a i > 1
性质
这里我们介绍最重要的两个性质:
莫比乌斯函数为积性函数,即如果 a ⊥ b a \perp b a ⊥ b ,则 μ ( a b ) = μ ( a ) μ ( b ) \mu(ab)=\mu(a)\mu(b) μ ( ab ) = μ ( a ) μ ( b )
这里我们使用最基础的方法来证明一下。
假设
gcd ( a , b ) = 1 \gcd(a, b) = 1 g cd( a , b ) = 1 。
若
a a a 或
b b b 中有一个包含平方因子。则
a b ab ab 必然也包含平方因子。此时
μ ( a ) μ ( b ) = 0 \mu(a)\mu(b) = 0 μ ( a ) μ ( b ) = 0 ,而
μ ( a b ) = 0 \mu(ab) = 0 μ ( ab ) = 0 。等式成立。
若
a a a 和
b b b 都不含平方因子。设
a = p 1 p 2 … p k a = p_1 p_2 \dots p_k a = p 1 p 2 … p k ,
b = q 1 q 2 … q m b = q_1 q_2 \dots q_m b = q 1 q 2 … q m 。因为
gcd ( a , b ) = 1 \gcd(a, b) = 1 g cd( a , b ) = 1 ,所以所有的质数
{ p i } \{p_i\} { p i } 和
{ q j } \{q_j\} { q j } 互不相同。那么
a b ab ab 就是
k + m k+m k + m 个不同质数的乘积。根据定义:
μ ( a ) = ( − 1 ) k , μ ( b ) = ( − 1 ) m \mu(a) = (-1)^k, \quad \mu(b) = (-1)^m μ ( a ) = ( − 1 ) k , μ ( b ) = ( − 1 ) m
μ ( a b ) = ( − 1 ) k + m = ( − 1 ) k ⋅ ( − 1 ) m = μ ( a ) μ ( b ) \mu(ab) = (-1)^{k+m} = (-1)^k \cdot (-1)^m = \mu(a)\mu(b) μ ( ab ) = ( − 1 ) k + m = ( − 1 ) k ⋅ ( − 1 ) m = μ ( a ) μ ( b )
证明完毕。
还有一种用狄利克雷卷积证明的办法,感兴趣的读者可以下去自行探索。
这个性质解释了为什么我们能够用线性筛把
1 ∼ n 1\sim n 1 ∼ n 之间的
μ \mu μ 值在
O ( n ) O(n) O ( n ) 的复杂度内算出。
具体的,当我们处理
i × p r i m e [ j ] i \times prime[j] i × p r im e [ j ] 时,
如果两者互质,利用积性性质:m u [ i × p r i m e [ j ] ] = m u [ i ] × m u [ p r i m e [ j ] ] mu[i \times prime[j]] = mu[i] \times mu[prime[j]] m u [ i × p r im e [ j ]] = m u [ i ] × m u [ p r im e [ j ]] ,
因为
p r i m e [ j ] prime[j] p r im e [ j ] 是质数,
μ ( p r i m e [ j ] ) \mu(prime[j]) μ ( p r im e [ j ]) 永远等于
− 1 -1 − 1 。
所以
μ ( i × p r i m e [ j ] ) = μ ( i ) × ( − 1 ) = − μ ( i ) \mu(i \times prime[j]) = \mu(i) \times (-1) = -\mu(i) μ ( i × p r im e [ j ]) = μ ( i ) × ( − 1 ) = − μ ( i ) 。
如果 p r i m e [ j ] ∣ i prime[j] \mid i p r im e [ j ] ∣ i ,说明出现了平方因子 p r i m e [ j ] 2 prime[j]^2 p r im e [ j ] 2 ,根据定义,结果直接为 0 0 0 。
线性筛代码
CPP inline void init (int limit)
{
fill (isprime + 1 , isprime + 1 + limit, true );
miu[1 ] = 1 ;
for (int i = 2 ; i <= limit; ++i)
{
if (isprime[i]) { prime[++sub] = i; miu[i] = -1 ; }
for (int j = 1 ; j <= sub && i * prime[j] <= limit; ++j)
{
isprime[i * prime[j]] = false ;
if (i % prime[j] == 0 )
{
miu[i * prime[j]] = 0 ;
break ;
}
else miu[i * prime[j]] = -miu[i];
}
}
}
因数和性质
∑ d ∣ n μ ( d ) = [ n = 1 ] = { 1 若 n = 1 0 若 n > 1 \sum_{d|n} \mu(d) = [n=1] = \begin{cases} 1 & \text{若 } n = 1 \\ 0 & \text{若 } n > 1 \end{cases} ∑ d ∣ n μ ( d ) = [ n = 1 ] = { 1 0 若 n = 1 若 n > 1
这个性质在莫比乌斯反演中非常的重要。
比如,当我们遇到“当且仅当
gcd ( i , j ) = 1 \gcd(i, j) = 1 g cd( i , j ) = 1 时贡献 1,否则贡献 0”的情况。我们发现判定
gcd ( i , j ) = 1 \gcd(i, j)=1 g cd( i , j ) = 1 非常的困难,这时候我们可以转换为
[ gcd ( i , j ) = 1 ] = ∑ d ∣ gcd ( i , j ) μ ( d ) [\gcd(i, j)=1] = \sum_{d|\gcd(i, j)} \mu(d) [ g cd( i , j ) = 1 ] = ∑ d ∣ g c d ( i , j ) μ ( d )
然后我们就可以通过交换求和次序,把复杂的
gcd \gcd g cd 计数转化为简单的倍数统计,这是所有莫比乌斯反演题目的通用套路。
所以,当我们遇到
gcd ( i , j ) = d \gcd(i, j)=d g cd( i , j ) = d 的问题,这道题有很大的概率要运用莫比乌斯反演进行解决。
证明的过程也比较简洁。设
n = p 1 a 1 … p k a k n = p_1^{a_1} \dots p_k^{a_k} n = p 1 a 1 … p k a k ,其中
k k k 为不同质因子的个数。
由于当因数
d d d 含有平方因子时
μ ( d ) = 0 \mu(d)=0 μ ( d ) = 0 ,因此有效的
d d d 只能是这
k k k 个质因子的某种组合。
我们将每一项
μ ( d ) \mu(d) μ ( d ) 的贡献看作是从
k k k 个质因子中选取
r r r 个的结果:
∑ d ∣ n μ ( d ) = ∑ r = 0 k ( k r ) ( − 1 ) r \sum_{d|n} \mu(d) = \sum_{r=0}^{k} \binom{k}{r} (-1)^r ∑ d ∣ n μ ( d ) = ∑ r = 0 k ( r k ) ( − 1 ) r
根据二项式定理
( x + y ) k = ∑ r = 0 k ( k r ) x k − r y r (x+y)^k = \sum_{r=0}^k \binom{k}{r} x^{k-r} y^r ( x + y ) k = ∑ r = 0 k ( r k ) x k − r y r ,我们令
x = 1 , y = − 1 x=1, y=-1 x = 1 , y = − 1 :
∑ r = 0 k ( k r ) ( − 1 ) r = ( 1 − 1 ) k = { 1 k = 0 (即 n = 1 ) 0 k ≥ 1 (即 n > 1 ) \sum_{r=0}^{k} \binom{k}{r} (-1)^r = (1 - 1)^k = \begin{cases} 1 & k=0 \text{ (即 } n=1) \\ 0 & k \ge 1 \text{ (即 } n>1) \end{cases} ∑ r = 0 k ( r k ) ( − 1 ) r = ( 1 − 1 ) k = { 1 0 k = 0 ( 即 n = 1 ) k ≥ 1 ( 即 n > 1 )
还有一些其他有意思的性质,由于做题的时候用不到,这里我们就不再介绍了。
莫比乌斯反演
定义
我们做题的时候一般把我们要求的目标函数定义为
f ( d ) f(d) f ( d ) ,再定义一个辅助函数
F ( n ) F(n) F ( n ) ,一般这个函数都比较好计算,然后
F ( n ) F(n) F ( n ) 和
f ( d ) f(d) f ( d ) 之间存在某种关系,最后通过
F ( n ) F(n) F ( n ) 反推出
f ( d ) f(d) f ( d ) ,然后通过一系列操作降低复杂度最后求得结果。
1. 约数反演
若有
F ( n ) = ∑ d ∣ n f ( d ) F(n) = \sum_{d|n} f(d) F ( n ) = ∑ d ∣ n f ( d ) ,则有:
f ( n ) = ∑ d ∣ n μ ( d ) F ( n d ) f(n) = \sum_{d|n} \mu(d) F\left(\frac{n}{d}\right) f ( n ) = ∑ d ∣ n μ ( d ) F ( d n )
证明:
首先给大家介绍几个东西。
狄利克雷卷积定义:
( a ∗ b ) ( n ) = ∑ d ∣ n a ( d ) b ( n d ) (a * b)(n) = \sum_{d|n} a(d)b(\frac{n}{d}) ( a ∗ b ) ( n ) = ∑ d ∣ n a ( d ) b ( d n ) 。它有着代数的运算性质
单位元:
ϵ ( n ) = [ n = 1 ] \epsilon(n) = [n=1] ϵ ( n ) = [ n = 1 ] 。任何函数
f ∗ ϵ = f f * \epsilon = f f ∗ ϵ = f 。
恒等函数:
1 ( n ) = 1 1(n) = 1 1 ( n ) = 1 。
已知
F = f ∗ 1 F = f * 1 F = f ∗ 1 (
F ( n ) = ∑ d ∣ n f ( d ) F(n) = \sum_{d|n} f(d) F ( n ) = ∑ d ∣ n f ( d ) )。
F ∗ μ = ( f ∗ 1 ) ∗ μ F * \mu = (f * 1) * \mu F ∗ μ = ( f ∗ 1 ) ∗ μ
F ∗ μ = f ∗ ( 1 ∗ μ ) F * \mu = f * (1 * \mu) F ∗ μ = f ∗ ( 1 ∗ μ )
容易得知,
μ ∗ 1 = ϵ \mu * 1 = \epsilon μ ∗ 1 = ϵ 。
F ∗ μ = f ∗ ϵ = f F * \mu = f * \epsilon = f F ∗ μ = f ∗ ϵ = f
f = F ∗ μ f = F * \mu f = F ∗ μ ,即
f ( n ) = ∑ d ∣ n μ ( d ) F ( n d ) f(n) = \sum_{d|n} \mu(d) F(\frac{n}{d}) f ( n ) = ∑ d ∣ n μ ( d ) F ( d n ) 。证毕。
2. 倍数反演
倍数反演是竞赛中最常用的反演,绝大多数问题中运用的都是此反演。
若有
F ( n ) = ∑ n ∣ d f ( d ) F(n) = \sum_{n|d} f(d) F ( n ) = ∑ n ∣ d f ( d ) ,则有:
f ( n ) = ∑ n ∣ d μ ( d n ) F ( d ) f(n) = \sum_{n|d} \mu\left(\frac{d}{n}\right) F(d) f ( n ) = ∑ n ∣ d μ ( n d ) F ( d )
证明过程与上面相似,这里不再阐述。
3.指数反演
这个基本上不会用,貌似数学竞赛中有时候会用到。
若有
F ( n ) = ∏ d ∣ n f ( d ) F(n) = \prod_{d|n} f(d) F ( n ) = ∏ d ∣ n f ( d ) ,则有:
f ( n ) = ∏ d ∣ n F ( n d ) μ ( d ) f(n) = \prod_{d|n} F\left(\frac{n}{d}\right)^{\mu(d)} f ( n ) = ∏ d ∣ n F ( d n ) μ ( d )
做题思路
一般的思路是先将一个问题抽象成
gcd \gcd g cd 计数模型,
得到一个类似这样的式子:
A n s = ∑ i = 1 n ∑ j = 1 m [ gcd ( i , j ) = k ] Ans = \sum_{i=1}^{n} \sum_{j=1}^{m} [\gcd(i, j) = k] A n s = ∑ i = 1 n ∑ j = 1 m [ g cd( i , j ) = k ]
令
i ′ = i / k , j ′ = j / k i' = i/k, j' = j/k i ′ = i / k , j ′ = j / k
A n s = ∑ i ′ = 1 ⌊ n / k ⌋ ∑ j ′ = 1 ⌊ m / k ⌋ [ gcd ( i ′ , j ′ ) = 1 ] Ans = \sum_{i'=1}^{\lfloor n/k \rfloor} \sum_{j'=1}^{\lfloor m/k \rfloor} [\gcd(i', j') = 1] A n s = ∑ i ′ = 1 ⌊ n / k ⌋ ∑ j ′ = 1 ⌊ m / k ⌋ [ g cd( i ′ , j ′ ) = 1 ]
令
N = ⌊ n / k ⌋ , M = ⌊ m / k ⌋ N = \lfloor n/k \rfloor, M = \lfloor m/k \rfloor N = ⌊ n / k ⌋ , M = ⌊ m / k ⌋ 。
设
f ( d ) f(d) f ( d ) :表示
gcd ( i , j ) \gcd(i, j) g cd( i , j ) 恰好等于
d d d 的对数(我们最终想要
f ( 1 ) f(1) f ( 1 ) )。
F ( d ) F(d) F ( d ) :表示
gcd ( i , j ) \gcd(i, j) g cd( i , j ) 是
d d d 的倍数 的对数。
显然有
F ( d ) = ∑ d ∣ k f ( k ) F(d) = \sum_{d|k} f(k) F ( d ) = ∑ d ∣ k f ( k )
又可以推出
F ( d ) = ⌊ N d ⌋ ⋅ ⌊ M d ⌋ F(d) = \lfloor \frac{N}{d} \rfloor \cdot \lfloor \frac{M}{d} \rfloor F ( d ) = ⌊ d N ⌋ ⋅ ⌊ d M ⌋
f ( 1 ) = ∑ 1 ∣ d μ ( d 1 ) F ( d ) ⟹ ∑ d = 1 min ( N , M ) μ ( d ) ⋅ ⌊ N d ⌋ ⋅ ⌊ M d ⌋ f(1) = \sum_{1|d} \mu(\frac{d}{1}) F(d) \implies \sum_{d=1}^{\min(N,M)} \mu(d) \cdot \lfloor \frac{N}{d} \rfloor \cdot \lfloor \frac{M}{d} \rfloor f ( 1 ) = ∑ 1∣ d μ ( 1 d ) F ( d ) ⟹ ∑ d = 1 m i n ( N , M ) μ ( d ) ⋅ ⌊ d N ⌋ ⋅ ⌊ d M ⌋
然后我们通过预处理或者整除分块操作来降低复杂度完成。
有时候我们需要用到求和顺序交换,提出因数
d d d ,结合狄利克雷卷积等经典操作,具体的在后面题目中会讲解。
简单的变形,如求
∑ ∑ gcd ( i , j ) \sum \sum \gcd(i, j) ∑∑ g cd( i , j ) ,我们可以枚举
gcd \gcd g cd 的值:
∑ g = 1 min ( n , m ) g ⋅ ∑ i = 1 n ∑ j = 1 m [ gcd ( i , j ) = g ] \sum_{g=1}^{\min(n, m)} g \cdot \sum_{i=1}^n \sum_{j=1}^m [\gcd(i, j) = g] ∑ g = 1 m i n ( n , m ) g ⋅ ∑ i = 1 n ∑ j = 1 m [ g cd( i , j ) = g ] 。后面就一直推下去就好。
整除分块
如果我们要计算
A n s = ∑ i = 1 n ⌊ n i ⌋ Ans = \sum_{i=1}^{n} \lfloor \frac{n}{i} \rfloor A n s = ∑ i = 1 n ⌊ i n ⌋
我们可以发现
⌊ n / i ⌋ \lfloor n/i \rfloor ⌊ n / i ⌋ 的值在一段连续的区间内是不变的。
我们想到可以把这些相同的块一次性算出来,就可以降低一定的复杂度。
假设当前块的左端点是
l l l ,我们需要找到最大的右端点
r r r ,使得对于所有
i ∈ [ l , r ] i \in [l, r] i ∈ [ l , r ] ,
⌊ n / i ⌋ \lfloor n/i \rfloor ⌊ n / i ⌋ 都相等。这个
r r r 的计算公式极其简洁:
r = ⌊ n ⌊ n / l ⌋ ⌋ r = \lfloor \frac{n}{\lfloor n/l \rfloor} \rfloor r = ⌊ ⌊ n / l ⌋ n ⌋
证明(简述):
设
k = ⌊ n / l ⌋ k = \lfloor n/l \rfloor k = ⌊ n / l ⌋ 。我们要找最大的
r r r 满足
r ⋅ k ≤ n r \cdot k \le n r ⋅ k ≤ n ,显然
r ≤ n / k r \le n/k r ≤ n / k 。向下取整即得
r = ⌊ n / k ⌋ r = \lfloor n/k \rfloor r = ⌊ n / k ⌋ 。
参考代码
CPP
long long ans = 0 ;
for (int l = 1 , r; l <= n; l = r + 1 )
{
r = n / (n / l);
ans += (long long )(r - l + 1 ) * (n / l);
}
下面讲解一些莫比乌斯反演好题,让大家能对这一算法有着更深的理解。
Eg1 \textit{\textbf {Eg1}} Eg1
这道题是 莫比乌斯反演 的经典入门题,所以拿出来作为讲解的第一道题。
题目简述
题目要求计算满足
a ≤ x ≤ b a \le x \le b a ≤ x ≤ b ,
c ≤ y ≤ d c \le y \le d c ≤ y ≤ d 且
gcd ( x , y ) = k \gcd(x, y) = k g cd( x , y ) = k 的数对
( x , y ) (x, y) ( x , y ) 的个数。
推导过程
由于区间
[ a , b ] [a, b] [ a , b ] 和
[ c , d ] [c, d] [ c , d ] 不是从 1 开始的,直接计算比较麻烦。我们可以利用二维前缀和的思想(容斥原理)将其转化为四个从
( 1 , 1 ) (1, 1) ( 1 , 1 ) 开始的子问题。
定义函数
s o l v e ( n , m , k ) solve(n, m, k) so l v e ( n , m , k ) 为:满足
1 ≤ x ≤ n 1 \le x \le n 1 ≤ x ≤ n ,
1 ≤ y ≤ m 1 \le y \le m 1 ≤ y ≤ m 且
gcd ( x , y ) = k \gcd(x, y) = k g cd( x , y ) = k 的数对数量。那么原问题的答案为:
A n s = solve ( b , d , k ) − solve ( a − 1 , d , k ) − solve ( b , c − 1 , k ) + solve ( a − 1 , c − 1 , k ) Ans = \text{solve}(b, d, k) - \text{solve}(a-1, d, k) - \text{solve}(b, c-1, k) + \text{solve}(a-1, c-1, k) A n s = solve ( b , d , k ) − solve ( a − 1 , d , k ) − solve ( b , c − 1 , k ) + solve ( a − 1 , c − 1 , k )
现在我们的核心任务是快速求出
s o l v e ( n , m , k ) solve(n, m, k) so l v e ( n , m , k ) 。
solve ( n , m , k ) = ∑ x = 1 n ∑ y = 1 m [ gcd ( x , y ) = k ] \text{solve}(n, m, k) = \sum_{x=1}^{n} \sum_{y=1}^{m} [\gcd(x, y) = k] solve ( n , m , k ) = ∑ x = 1 n ∑ y = 1 m [ g cd( x , y ) = k ]
gcd ( x , y ) = k \gcd(x, y) = k g cd( x , y ) = k 等价于
gcd ( x / k , y / k ) = 1 \gcd(x/k, y/k) = 1 g cd( x / k , y / k ) = 1 。令
n ′ = ⌊ n / k ⌋ , m ′ = ⌊ m / k ⌋ n' = \lfloor n/k \rfloor, m' = \lfloor m/k \rfloor n ′ = ⌊ n / k ⌋ , m ′ = ⌊ m / k ⌋ 。问题转化为求
1 ≤ i ≤ n ′ , 1 ≤ j ≤ m ′ 1 \le i \le n', 1 \le j \le m' 1 ≤ i ≤ n ′ , 1 ≤ j ≤ m ′ 且
gcd ( i , j ) = 1 \gcd(i, j) = 1 g cd( i , j ) = 1 的对数。
solve ( n , m , k ) = ∑ i = 1 n ′ ∑ j = 1 m ′ [ gcd ( i , j ) = 1 ] \text{solve}(n, m, k) = \sum_{i=1}^{n'} \sum_{j=1}^{m'} [\gcd(i, j) = 1] solve ( n , m , k ) = ∑ i = 1 n ′ ∑ j = 1 m ′ [ g cd( i , j ) = 1 ]
我们定义
f ( d ) f(d) f ( d ) :满足 gcd ( i , j ) = d \gcd(i, j) = d g cd( i , j ) = d 的对数。
F ( d ) F(d) F ( d ) :满足 d ∣ gcd ( i , j ) d \mid \gcd(i, j) d ∣ g cd( i , j ) 的对数
显然有关系:
F ( d ) = ∑ d ∣ K f ( K ) F(d) = \sum_{d|K} f(K) F ( d ) = ∑ d ∣ K f ( K ) 。
同时,
d ∣ gcd ( i , j ) d \mid \gcd(i, j) d ∣ g cd( i , j ) 意味着
i i i 是
d d d 的倍数且
j j j 是
d d d 的倍数。在
1 … n ′ 1 \dots n' 1 … n ′ 中,
d d d 的倍数有
⌊ n ′ / d ⌋ \lfloor n'/d \rfloor ⌊ n ′ / d ⌋ 个;在
1 … m ′ 1 \dots m' 1 … m ′ 中有
⌊ m ′ / d ⌋ \lfloor m'/d \rfloor ⌊ m ′ / d ⌋ 个。故直接得出:
F ( d ) = ⌊ n ′ d ⌋ ⌊ m ′ d ⌋ F(d) = \lfloor \frac{n'}{d} \rfloor \lfloor \frac{m'}{d} \rfloor F ( d ) = ⌊ d n ′ ⌋ ⌊ d m ′ ⌋
根据倍数形式的反演公式:
f ( d ) = ∑ d ∣ K μ ( K d ) F ( K ) f(d) = \sum_{d|K} \mu(\frac{K}{d}) F(K) f ( d ) = ∑ d ∣ K μ ( d K ) F ( K ) 。取
d = 1 d=1 d = 1 ,代入
F ( K ) F(K) F ( K ) :
f ( 1 ) = ∑ K = 1 min ( n ′ , m ′ ) μ ( K ) ⋅ ⌊ n ′ K ⌋ ⌊ m ′ K ⌋ f(1) = \sum_{K=1}^{\min(n', m')} \mu(K) \cdot \lfloor \frac{n'}{K} \rfloor \lfloor \frac{m'}{K} \rfloor f ( 1 ) = ∑ K = 1 m i n ( n ′ , m ′ ) μ ( K ) ⋅ ⌊ K n ′ ⌋ ⌊ K m ′ ⌋
虽然推导出了
f ( 1 ) = ∑ i = 1 min ( n ′ , m ′ ) μ ( i ) ⌊ n ′ i ⌋ ⌊ m ′ i ⌋ f(1) = \sum_{i=1}^{\min(n', m')} \mu(i) \lfloor \frac{n'}{i} \rfloor \lfloor \frac{m'}{i} \rfloor f ( 1 ) = ∑ i = 1 m i n ( n ′ , m ′ ) μ ( i ) ⌊ i n ′ ⌋ ⌊ i m ′ ⌋ ,但单次询问
O ( N ) O(N) O ( N ) 会超时,我们考虑用怎么优化。
我们可以发现后面那一堆是整除分块的经典形式,即对于
⌊ n ′ / i ⌋ \lfloor n'/i \rfloor ⌊ n ′ / i ⌋ ,当
i i i 在一定范围内变化时,其值是不变的。
我们可以预处理
μ \mu μ 的前缀和
S ( i ) = ∑ j = 1 i μ ( j ) S(i) = \sum_{j=1}^i \mu(j) S ( i ) = ∑ j = 1 i μ ( j ) 。对于每一块
[ l , r ] [l, r] [ l , r ] ,其贡献为:
( S ( r ) − S ( l − 1 ) ) ⋅ ⌊ n ′ l ⌋ ⋅ ⌊ m ′ l ⌋ (S(r) - S(l-1)) \cdot \lfloor \frac{n'}{l} \rfloor \cdot \lfloor \frac{m'}{l} \rfloor ( S ( r ) − S ( l − 1 )) ⋅ ⌊ l n ′ ⌋ ⋅ ⌊ l m ′ ⌋
其中右边界
r = min ( n ′ / ( n ′ / l ) , m ′ / ( m ′ / l ) ) r = \min(n' / (n'/l), m' / (m'/l)) r = min ( n ′ / ( n ′ / l ) , m ′ / ( m ′ / l )) 。
最终
s o l v e ( n , m , k ) solve(n, m, k) so l v e ( n , m , k ) 的计算公式为:
s o l v e ( n , m , k ) = ∑ i = 1 min ( ⌊ n / k ⌋ , ⌊ m / k ⌋ ) μ ( i ) ⌊ n k i ⌋ ⌊ m k i ⌋ solve(n, m, k) = \sum_{i=1}^{\min(\lfloor n/k \rfloor, \lfloor m/k \rfloor)} \mu(i) \lfloor \frac{n}{ki} \rfloor \lfloor \frac{m}{ki} \rfloor so l v e ( n , m , k ) = ∑ i = 1 m i n (⌊ n / k ⌋ , ⌊ m / k ⌋) μ ( i ) ⌊ ki n ⌋ ⌊ ki m ⌋
参考代码
CPP #include <bits/stdc++.h>
#define ll long long
using namespace std;
const int MAX = 5e4 + 10 ;
int Q;
int prime[MAX], sub;
bool isprime[MAX];
int miu[MAX], sum[MAX];
inline void init (int limit)
{
fill (isprime + 1 , isprime + 1 + limit, true );
isprime[1 ] = false ;
miu[1 ] = 1 ;
for (int i = 2 ; i <= limit; ++i)
{
if (isprime[i]) { prime[++sub] = i; miu[i] = -1 ; }
for (int j = 1 ; j <= sub && i * prime[j] <= limit; ++j)
{
isprime[i * prime[j]] = false ;
if (i % prime[j] == 0 )
{
miu[i * prime[j]] = 0 ;
break ;
}
else miu[i * prime[j]] = -miu[i];
}
}
for (int i = 1 ; i <= limit; ++i) sum[i] = sum[i - 1 ] + miu[i];
}
inline ll calc (int n, int m)
{
if (n > m) swap (n, m);
ll res = 0 ;
for (int l = 1 , r; l <= n; l = r + 1 )
{
r = min (n / (n / l), m / (m / l));
res += (ll)(sum[r] - sum[l - 1 ]) * (n / l) * (m / l);
}
return res;
}
int main ()
{
ios::sync_with_stdio (false );
cin.tie (0 ); cout.tie (0 );
init (50000 );
cin >> Q;
while (Q--)
{
int a, b, c, d, k; cin >> a >> b >> c >> d >> k;
ll ans = calc (b / k, d / k)
- calc ((a - 1 ) / k, d / k)
- calc (b / k, (c - 1 ) / k)
+ calc ((a - 1 ) / k, (c - 1 ) / k);
cout << ans << "\n" ;
}
return 0 ;
}
Eg2 \textit{\textbf {Eg2}} Eg2
这道题是一道经典的数论题目。要解决它,我们需要将视觉问题转化为数学模型。
推导过程
1. 建立坐标系
假设 C 君的位置在坐标原点 ( 0 , 0 ) (0,0) ( 0 , 0 ) 。那么学生方阵占据的坐标范围是从 ( 0 , 0 ) (0,0) ( 0 , 0 ) 到 ( N − 1 , N − 1 ) (N-1, N-1) ( N − 1 , N − 1 ) 。
2. 判定“能看到”的条件
一个点
( x , y ) (x, y) ( x , y ) 能被看到,当且仅当在 C 君的视线上,它是第一个点。
如果存在另一个点
( x ′ , y ′ ) (x', y') ( x ′ , y ′ ) 在连线
O ( x , y ) O(x, y) O ( x , y ) 上且更靠近原点,那么
( x , y ) (x, y) ( x , y ) 就会被挡住。
这意味着
x x x 和
y y y 必须互质。
如果
gcd ( x , y ) = d > 1 \gcd(x, y) = d > 1 g cd( x , y ) = d > 1 ,那么点
( x d , y d ) (\frac{x}{d}, \frac{y}{d}) ( d x , d y ) 就会挡住
( x , y ) (x, y) ( x , y ) 。
因此,问题转化为:在
0 ≤ x , y < N 0 \le x, y < N 0 ≤ x , y < N 的范围内,有多少对
( x , y ) (x, y) ( x , y ) 满足
gcd ( x , y ) = 1 \gcd(x, y) = 1 g cd( x , y ) = 1 ?
3. 特殊情况处理
当
N = 1 N=1 N = 1 时,方阵里其实没人,或者说只有 C 君自己,输出
0 0 0 (或者根据具体定义可能是
0 0 0 )。但在本题数据范围内,
N = 1 N=1 N = 1 需要特判,通常这种视野题
N = 1 N=1 N = 1 结果为
0 0 0 。
点
( 1 , 0 ) (1,0) ( 1 , 0 ) ,
( 0 , 1 ) (0,1) ( 0 , 1 ) 和
( 1 , 1 ) (1,1) ( 1 , 1 ) 是必然能看到的。
推导式子
这道题能单纯用欧拉函数推导出来,但是为了练习莫比乌斯反演,这里我们从莫比乌斯反演的角度来推导一下。
我们先不考虑坐标轴上的点。
设
n = N − 1 n = N-1 n = N − 1 ,我们需要求:
∑ i = 1 n ∑ j = 1 n [ gcd ( i , j ) = 1 ] \sum_{i=1}^{n} \sum_{j=1}^{n} [\gcd(i, j) = 1] ∑ i = 1 n ∑ j = 1 n [ g cd( i , j ) = 1 ]
定义 f ( d ) f(d) f ( d ) :满足 gcd ( x , y ) = d \gcd(x, y) = d g cd( x , y ) = d 的数对 ( x , y ) (x, y) ( x , y ) 的个数,其中 1 ≤ x , y ≤ n 1 \le x, y \le n 1 ≤ x , y ≤ n 。
定义 F ( d ) F(d) F ( d ) :满足 d ∣ gcd ( x , y ) d | \gcd(x, y) d ∣ g cd( x , y ) 的数对 ( x , y ) (x, y) ( x , y ) 的个数,其中 1 ≤ x , y ≤ n 1 \le x, y \le n 1 ≤ x , y ≤ n 。
显然,如果一个数对的公约数是
d d d 的倍数,那么它们的最大公约数一定是
d , 2 d , 3 d … d, 2d, 3d \dots d , 2 d , 3 d … 中的某一个。
F ( d ) = ∑ d ∣ k , k ≤ n f ( k ) F(d) = \sum_{d|k, k \le n} f(k) F ( d ) = ∑ d ∣ k , k ≤ n f ( k )
同时,根据定义,
1 … n 1 \dots n 1 … n 范围内
d d d 的倍数有
⌊ n d ⌋ \lfloor \frac{n}{d} \rfloor ⌊ d n ⌋ 个。要让
x x x 和
y y y 都是
d d d 的倍数,组合数就是:
F ( d ) = ⌊ n d ⌋ 2 F(d) = \lfloor \frac{n}{d} \rfloor^2 F ( d ) = ⌊ d n ⌋ 2
根据莫比乌斯反演的第二种形式(倍数形式):若
F ( d ) = ∑ d ∣ k f ( k ) F(d) = \sum_{d|k} f(k) F ( d ) = ∑ d ∣ k f ( k ) ,则有:
f ( d ) = ∑ d ∣ k , k ≤ n μ ( k d ) F ( k ) f(d) = \sum_{d|k, k \le n} \mu(\frac{k}{d}) F(k) f ( d ) = ∑ d ∣ k , k ≤ n μ ( d k ) F ( k )
我们要求的是
f ( 1 ) f(1) f ( 1 ) ,代入公式:
f ( 1 ) = ∑ 1 ∣ k , k ≤ n μ ( k 1 ) F ( k ) f(1) = \sum_{1|k, k \le n} \mu(\frac{k}{1}) F(k) f ( 1 ) = ∑ 1∣ k , k ≤ n μ ( 1 k ) F ( k )
将
F ( k ) = ⌊ n k ⌋ 2 F(k) = \lfloor \frac{n}{k} \rfloor^2 F ( k ) = ⌊ k n ⌋ 2 代入上式,得到最终计算公式:
f ( 1 ) = ∑ k = 1 n μ ( k ) ⌊ n k ⌋ 2 f(1) = \sum_{k=1}^{n} \mu(k) \lfloor \frac{n}{k} \rfloor^2 f ( 1 ) = ∑ k = 1 n μ ( k ) ⌊ k n ⌋ 2
说明
最后只需要加上坐标轴上的两个点即可
参考代码
CPP #include <bits/stdc++.h>
using namespace std;
const int N = 4e4 + 10 ;
const int MAX = 4e4 ;
int n;
bool isprime[N];
int miu[N], prime[N], sub;
inline void initmiu ()
{
memset (isprime, true , sizeof isprime);
miu[1 ] = 1 ;
for (int i = 2 ; i <= MAX; ++i)
{
if (isprime[i]) { miu[i] = -1 ; prime[++sub] = i; }
for (int j = 1 ; j <= sub && i * prime[j] <= MAX; ++j)
{
isprime[i * prime[j]] = false ;
if (i % prime[j] == 0 ) { miu[i * prime[j]] = 0 ; break ; }
else miu[i * prime[j]] = -miu[i];
}
}
}
int main ()
{
scanf ("%d" , &n);
if (n == 1 ) { puts ("0" ); exit (0 ); }
initmiu ();
long long ans = 0 ;
n--;
for (int i = 1 ; i <= n; ++i)
{
ans += (long long ) miu[i] * (n / i) * (n / i);
}
printf ("%lld\n" , ans + 2 );
return 0 ;
}
Eg3 \textit{\textbf {Eg3}} Eg3
题目简述
求解
Ans = ∑ i = 1 n ∑ j = 1 m lcm ( i , j ) ( m o d 20101009 ) \text{Ans} = \sum_{i=1}^n \sum_{j=1}^m \text{lcm}(i, j) \pmod{20101009} Ans = ∑ i = 1 n ∑ j = 1 m lcm ( i , j ) ( mod 20101009 )
推导过程
利用基本性质
lcm ( i , j ) = i ⋅ j gcd ( i , j ) \text{lcm}(i, j) = \frac{i \cdot j}{\gcd(i, j)} lcm ( i , j ) = g c d ( i , j ) i ⋅ j ,公式变为:
Ans = ∑ i = 1 n ∑ j = 1 m i ⋅ j gcd ( i , j ) \text{Ans} = \sum_{i=1}^n \sum_{j=1}^m \frac{i \cdot j}{\gcd(i, j)} Ans = ∑ i = 1 n ∑ j = 1 m g c d ( i , j ) i ⋅ j
依照一贯的做法,我们看到
gcd ( i , j ) \gcd(i, j) g cd( i , j ) 可以选择提取出来
gcd ( i , j ) \gcd(i, j) g cd( i , j ) 的所有取值。
这里我们枚举最大公约数
d d d 。当
gcd ( i , j ) = d \gcd(i, j) = d g cd( i , j ) = d 时,这一项的贡献是
i ⋅ j d \frac{i \cdot j}{d} d i ⋅ j 。
公式变形为:
Ans = ∑ d = 1 min ( n , m ) 1 d ∑ i = 1 n ∑ j = 1 m [ gcd ( i , j ) = d ] ⋅ i ⋅ j \text{Ans} = \sum_{d=1}^{\min(n, m)} \frac{1}{d} \sum_{i=1}^n \sum_{j=1}^m [\gcd(i, j) = d] \cdot i \cdot j Ans = ∑ d = 1 m i n ( n , m ) d 1 ∑ i = 1 n ∑ j = 1 m [ g cd( i , j ) = d ] ⋅ i ⋅ j
Ans = ∑ d = 1 min ( n , m ) d ∑ i = 1 n / d ∑ j = 1 m / d [ gcd ( i , j ) = 1 ] ⋅ i ⋅ j \text{Ans} = \sum_{d=1}^{\min(n, m)}d\sum_{i=1}^{n/d} \sum_{j=1}^{m/d} [\gcd(i, j) = 1] \cdot i \cdot j Ans = ∑ d = 1 m i n ( n , m ) d ∑ i = 1 n / d ∑ j = 1 m / d [ g cd( i , j ) = 1 ] ⋅ i ⋅ j
看得出来,后面这一堆是莫比乌斯反演的经典应用,因此我们考虑怎么优化
∑ i = 1 n / d ∑ j = 1 m / d [ gcd ( i , j ) = 1 ] ⋅ i ⋅ j \sum_{i=1}^{n/d} \sum_{j=1}^{m/d} [\gcd(i, j) = 1] \cdot i \cdot j ∑ i = 1 n / d ∑ j = 1 m / d [ g cd( i , j ) = 1 ] ⋅ i ⋅ j
根据莫比乌斯反演的应用,我们一般设
f ( d ) f(d) f ( d ) 为目标函数,所以这里我们设
f ( d ) f(d) f ( d ) 为
gcd ( i , j ) \gcd(i, j) g cd( i , j ) 恰好等于
d d d 的
i ⋅ j i \cdot j i ⋅ j 之和:
f ( d ) = ∑ i = 1 n ∑ j = 1 m [ gcd ( i , j ) = d ] ⋅ i ⋅ j f(d) = \sum_{i=1}^n \sum_{j=1}^m [\gcd(i, j) = d] \cdot i \cdot j f ( d ) = ∑ i = 1 n ∑ j = 1 m [ g cd( i , j ) = d ] ⋅ i ⋅ j
按照反演的套路,我们需要定义辅助函数
G ( k ) G(k) G ( k ) :
G ( k ) = ∑ k ∣ d f ( d ) = ∑ i = 1 n ∑ j = 1 m [ k ∣ gcd ( i , j ) ] ⋅ i ⋅ j G(k) = \sum_{k|d} f(d) = \sum_{i=1}^n \sum_{j=1}^m [k \mid \gcd(i, j)] \cdot i \cdot j G ( k ) = ∑ k ∣ d f ( d ) = ∑ i = 1 n ∑ j = 1 m [ k ∣ g cd( i , j )] ⋅ i ⋅ j
然后我们能发现这里
G ( k ) G(k) G ( k ) 的意义是:
G ( k ) G(k) G ( k ) 为
gcd ( i , j ) \gcd(i, j) g cd( i , j ) 是
k k k 的倍数 的
i ⋅ j i \cdot j i ⋅ j 之和。
那么按照反演的倍数公式,
f ( d ) = ∑ d ∣ k μ ( k d ) G ( k ) f(d) = \sum_{d|k} \mu(\frac{k}{d}) G(k) f ( d ) = ∑ d ∣ k μ ( d k ) G ( k )
我们现在考虑如何计算
F ( k ) F(k) F ( k ) 进而计算出我们的目标函数。
条件
k ∣ gcd ( i , j ) k \mid \gcd(i, j) k ∣ g cd( i , j ) 等价于
k ∣ i k|i k ∣ i 且
k ∣ j k|j k ∣ j 。
我们可以设
i = k ⋅ a , j = k ⋅ b i = k \cdot a, j = k \cdot b i = k ⋅ a , j = k ⋅ b ,代入式子:
G ( k ) = ∑ a = 1 ⌊ n / k ⌋ ∑ b = 1 ⌊ m / k ⌋ ( k ⋅ a ) ⋅ ( k ⋅ b ) G(k) = \sum_{a=1}^{\lfloor n/k \rfloor} \sum_{b=1}^{\lfloor m/k \rfloor} (k \cdot a) \cdot (k \cdot b) G ( k ) = ∑ a = 1 ⌊ n / k ⌋ ∑ b = 1 ⌊ m / k ⌋ ( k ⋅ a ) ⋅ ( k ⋅ b )
G ( k ) = k 2 ( ∑ a = 1 ⌊ n / k ⌋ a ) ( ∑ b = 1 ⌊ m / k ⌋ b ) G(k) = k^2 \left( \sum_{a=1}^{\lfloor n/k \rfloor} a \right) \left( \sum_{b=1}^{\lfloor m/k \rfloor} b \right) G ( k ) = k 2 ( ∑ a = 1 ⌊ n / k ⌋ a ) ( ∑ b = 1 ⌊ m / k ⌋ b )
如果我们令等差数列求和函数
g ( x ) = x ( x + 1 ) 2 g(x) = \frac{x(x+1)}{2} g ( x ) = 2 x ( x + 1 ) ,那么:
G ( k ) = k 2 ⋅ g ( ⌊ n / k ⌋ ) ⋅ g ( ⌊ m / k ⌋ ) G(k) = k^2 \cdot g(\lfloor n/k \rfloor) \cdot g(\lfloor m/k \rfloor) G ( k ) = k 2 ⋅ g (⌊ n / k ⌋) ⋅ g (⌊ m / k ⌋)
此时我们将将
F ( k ) F(k) F ( k ) 代入
f ( d ) f(d) f ( d ) :
f ( d ) = ∑ d ∣ k μ ( k d ) ⋅ k 2 ⋅ g ( ⌊ n / k ⌋ ) ⋅ g ( ⌊ m / k ⌋ ) f(d) = \sum_{d|k} \mu(\frac{k}{d}) \cdot k^2 \cdot g(\lfloor n/k \rfloor) \cdot g(\lfloor m/k \rfloor) f ( d ) = ∑ d ∣ k μ ( d k ) ⋅ k 2 ⋅ g (⌊ n / k ⌋) ⋅ g (⌊ m / k ⌋)
为了方便计算,令
k = d ⋅ t k = d \cdot t k = d ⋅ t :
f ( d ) = ∑ t = 1 min ( n / d , m / d ) μ ( t ) ⋅ ( d t ) 2 ⋅ g ( ⌊ n d t ⌋ ) ⋅ g ( ⌊ m d t ⌋ ) f(d) = \sum_{t=1}^{\min(n/d, m/d)} \mu(t) \cdot (dt)^2 \cdot g(\lfloor \frac{n}{dt} \rfloor) \cdot g(\lfloor \frac{m}{dt} \rfloor) f ( d ) = ∑ t = 1 m i n ( n / d , m / d ) μ ( t ) ⋅ ( d t ) 2 ⋅ g (⌊ d t n ⌋) ⋅ g (⌊ d t m ⌋)
我们最终的答案:
Ans = ∑ d = 1 min ( n , m ) 1 d ⋅ f ( d ) \text{Ans} = \sum_{d=1}^{\min(n, m)} \frac{1}{d} \cdot f(d) Ans = ∑ d = 1 m i n ( n , m ) d 1 ⋅ f ( d )
Ans = ∑ d = 1 1 d ∑ t = 1 μ ( t ) ⋅ d 2 t 2 ⋅ g ( ⌊ n d t ⌋ ) ⋅ g ( ⌊ m d t ⌋ ) \text{Ans} = \sum_{d=1} \frac{1}{d} \sum_{t=1} \mu(t) \cdot d^2 t^2 \cdot g(\lfloor \frac{n}{dt} \rfloor) \cdot g(\lfloor \frac{m}{dt} \rfloor) Ans = ∑ d = 1 d 1 ∑ t = 1 μ ( t ) ⋅ d 2 t 2 ⋅ g (⌊ d t n ⌋) ⋅ g (⌊ d t m ⌋)
Ans = ∑ d = 1 d ∑ t = 1 μ ( t ) ⋅ t 2 ⋅ g ( ⌊ n d t ⌋ ) ⋅ g ( ⌊ m d t ⌋ ) \text{Ans} = \sum_{d=1} d \sum_{t=1} \mu(t) \cdot t^2 \cdot g(\lfloor \frac{n}{dt} \rfloor) \cdot g(\lfloor \frac{m}{dt} \rfloor) Ans = ∑ d = 1 d ∑ t = 1 μ ( t ) ⋅ t 2 ⋅ g (⌊ d t n ⌋) ⋅ g (⌊ d t m ⌋)
如果此时我们把这个当做最后的式子去进行计算的话,可以发现程序逻辑是:
枚举 d d d 从 1 1 1 到 n n n 。
在每个 d d d 内部,枚举 t t t 从 1 1 1 到 n / d n/d n / d 。
计算 μ ( t ) t 2 … \mu(t) t^2 \dots μ ( t ) t 2 …
这个调和级数求和的复杂度是
O ( N log N ) O(N \log N) O ( N log N ) 。
在
N = 10 7 N=10^7 N = 1 0 7 的情况下,
10 7 × log ( 10 7 ) ≈ 1.6 × 10 8 10^7 \times \log(10^7) \approx 1.6 \times 10^8 1 0 7 × log ( 1 0 7 ) ≈ 1.6 × 1 0 8 ,勉强能跑,但是考虑到我们有大量的取模操作,加上一些神秘的常数,可能导致这道题过不去,最后还需要各种卡常操作,所以我们可以再考虑一下怎么优化最后这个式子。
观察我们上面的式子:
Ans = ∑ d = 1 d ∑ t = 1 μ ( t ) ⋅ t 2 ⋅ g ( ⌊ n d t ⌋ ) ⋅ g ( ⌊ m d t ⌋ ) \text{Ans} = \sum_{d=1} d \sum_{t=1} \mu(t) \cdot t^2 \cdot g(\lfloor \frac{n}{dt} \rfloor) \cdot g(\lfloor \frac{m}{dt} \rfloor) Ans = ∑ d = 1 d ∑ t = 1 μ ( t ) ⋅ t 2 ⋅ g (⌊ d t n ⌋) ⋅ g (⌊ d t m ⌋)
不难发现,只要
d × t d \times t d × t 的积相同,后面的
g g g 部分就是一样的。那我们为什么不把后面重复算的一堆
提取公因式 合在一起算呢?
我的语言描述能力不太好,可能你听的有点懵,具体得,我们令
T = d ⋅ t T = d \cdot t T = d ⋅ t 。现在我们不再先枚举
d d d 再枚举
t t t ,而是直接枚举这个乘积
T T T 。
Ans = ∑ T = 1 min ( n , m ) ( ∑ d ⋅ t = T d ⋅ μ ( t ) ⋅ t 2 ) ⋅ g ( ⌊ n T ⌋ ) ⋅ g ( ⌊ m T ⌋ ) \text{Ans} = \sum_{T=1}^{\min(n, m)} \left( \sum_{d \cdot t = T} d \cdot \mu(t) \cdot t^2 \right) \cdot g(\lfloor \frac{n}{T} \rfloor) \cdot g(\lfloor \frac{m}{T} \rfloor) Ans = ∑ T = 1 m i n ( n , m ) ( ∑ d ⋅ t = T d ⋅ μ ( t ) ⋅ t 2 ) ⋅ g (⌊ T n ⌋) ⋅ g (⌊ T m ⌋)
括号里面的 = ∑ d ∣ T d ⋅ μ ( T d ) ⋅ T 2 d 2 \text{括号里面的} = \sum_{d|T} d \cdot \mu(\frac{T}{d}) \cdot \frac{T^2}{d^2} 括号里面的 = ∑ d ∣ T d ⋅ μ ( d T ) ⋅ d 2 T 2
括号里面的 = ∑ d ∣ T T 2 d μ ( T d ) \text{括号里面的} = \sum_{d|T} \frac{T^2}{d} \mu(\frac{T}{d}) 括号里面的 = ∑ d ∣ T d T 2 μ ( d T )
括号里面的 = T ∑ d ∣ T T d μ ( T d ) \text{括号里面的} = T \sum_{d|T} \frac{T}{d} \mu(\frac{T}{d}) 括号里面的 = T ∑ d ∣ T d T μ ( d T )
令
k = T d k = \frac{T}{d} k = d T ,当
d d d 遍历
T T T 的所有因子时,
k k k 也会遍历
T T T 的所有因子。所以:
括号里面的 = T ∑ k ∣ T k ⋅ μ ( k ) \text{括号里面的} = T \sum_{k|T} k \cdot \mu(k) 括号里面的 = T ∑ k ∣ T k ⋅ μ ( k )
我们把括号里面的这一堆记进一个函数
F ( T ) F(T) F ( T ) ,原式就变成了:
Ans = ∑ T = 1 min ( n , m ) F ( T ) ⋅ g ( ⌊ n T ⌋ ) ⋅ g ( ⌊ m T ⌋ ) \text{Ans} = \sum_{T=1}^{\min(n, m)} F(T) \cdot g(\lfloor \frac{n}{T} \rfloor) \cdot g(\lfloor \frac{m}{T} \rfloor) Ans = ∑ T = 1 m i n ( n , m ) F ( T ) ⋅ g (⌊ T n ⌋) ⋅ g (⌊ T m ⌋)
这样,由于
⌊ n / T ⌋ \lfloor n/T \rfloor ⌊ n / T ⌋ 和
⌊ m / T ⌋ \lfloor m/T \rfloor ⌊ m / T ⌋ 在一段连续的
T T T 范围内取值是不变的(整除分块性质),我们可以一次性处理一个区间的
T T T ,即运用
整除分块 进行快速计算。
并且,如果我们能预处理出
F ( T ) F(T) F ( T ) 的前缀和
S u m F ( T ) = ∑ i = 1 T F ( i ) Sum_F(T) = \sum_{i=1}^T F(i) S u m F ( T ) = ∑ i = 1 T F ( i )
那么对于一段区间
[ l , r ] [l, r] [ l , r ] ,它们的贡献就是:
( S u m F ( r ) − S u m F ( l − 1 ) ) ⋅ g ( ⌊ n / l ⌋ ) ⋅ g ( ⌊ m / l ⌋ ) (Sum_F(r) - Sum_F(l-1)) \cdot g(\lfloor n/l \rfloor) \cdot g(\lfloor m/l \rfloor) ( S u m F ( r ) − S u m F ( l − 1 )) ⋅ g (⌊ n / l ⌋) ⋅ g (⌊ m / l ⌋)
现在,我们只剩一个问题:如何快速计算
F ( T ) F(T) F ( T ) ?
不难发现,
F ( T ) = T ⋅ ( I d ∗ μ ) ( T ) F(T) = T \cdot (Id * \mu)(T) F ( T ) = T ⋅ ( I d ∗ μ ) ( T ) ,其中
∗ * ∗ 是狄利克雷卷积,
I d Id I d 是单位函数
I d ( n ) = n Id(n)=n I d ( n ) = n 。
因为
I d Id I d 和
μ \mu μ 都是积性函数,它们的卷积也是积性的。
感性理解一下,意思就是:
h ( T ) = ∑ k ∣ T k μ ( k ) h(T) = \sum_{k|T} k \mu(k) h ( T ) = ∑ k ∣ T k μ ( k )
是积性的。
所以:
F ( T ) = T ⋅ h ( T ) F(T) = T \cdot h(T) F ( T ) = T ⋅ h ( T )
也是积性的。
对于积性函数,我们只需要关心它在质数
p p p 以及 质数的幂
p k p^k p k 处的取值,就可以用线性筛
O ( n ) O(n) O ( n ) 扫出来。
这是为什么呢?
根据积性函数的定义:
如果一个函数
f ( n ) f(n) f ( n ) 是积性函数,那么对于任何互质的
a a a 和
b b b (即
gcd ( a , b ) = 1 \gcd(a, b) = 1 g cd( a , b ) = 1 ),都有:
f ( a ⋅ b ) = f ( a ) ⋅ f ( b ) f(a \cdot b) = f(a) \cdot f(b) f ( a ⋅ b ) = f ( a ) ⋅ f ( b )
根据算术基本定理,任何一个大于 1 的正整数
n n n 都可以唯一分解为:
n = p 1 a 1 p 2 a 2 … p k a k n = p_1^{a_1} p_2^{a_2} \dots p_k^{a_k} n = p 1 a 1 p 2 a 2 … p k a k
由于不同的质数幂
p i a i p_i^{a_i} p i a i 之间一定是互质的,所以:
f ( n ) = f ( p 1 a 1 ) ⋅ f ( p 2 a 2 ) ⋅ ⋯ ⋅ f ( p k a k ) f(n) = f(p_1^{a_1}) \cdot f(p_2^{a_2}) \cdot \dots \cdot f(p_k^{a_k}) f ( n ) = f ( p 1 a 1 ) ⋅ f ( p 2 a 2 ) ⋅ ⋯ ⋅ f ( p k a k )
所以只要知道了
f f f 在所有“质数幂”处的值,就能通过乘法组合出
f f f 在任何整数处的值。
那么现在考虑
F ( T ) F(T) F ( T ) 在质数幂处的取值:
情况 1:T T T 是质数 p p p
h ( p ) = ∑ k ∣ p k μ ( k ) = 1 ⋅ μ ( 1 ) + p ⋅ μ ( p ) = 1 − p h(p) = \sum_{k|p} k \mu(k) = 1 \cdot \mu(1) + p \cdot \mu(p) = 1 - p h ( p ) = ∑ k ∣ p k μ ( k ) = 1 ⋅ μ ( 1 ) + p ⋅ μ ( p ) = 1 − p 。
所以:
F ( p ) = p ⋅ ( 1 − p ) F(p) = p \cdot (1 - p) F ( p ) = p ⋅ ( 1 − p ) 。
情况 2:T T T 是质数的高次幂 p a p^a p a (a ≥ 2 a \ge 2 a ≥ 2 )h ( p a ) = 1 μ ( 1 ) + p μ ( p ) + p 2 μ ( p 2 ) + ⋯ + p a μ ( p a ) h(p^a) = 1 \mu(1) + p \mu(p) + p^2 \mu(p^2) + \dots + p^a \mu(p^a) h ( p a ) = 1 μ ( 1 ) + p μ ( p ) + p 2 μ ( p 2 ) + ⋯ + p a μ ( p a ) 。
因为当
k ≥ 2 k \ge 2 k ≥ 2 时
μ ( p k ) = 0 \mu(p^k) = 0 μ ( p k ) = 0 ,所以这个长式子瞬间缩短为:
h ( p a ) = 1 − p h(p^a) = 1 - p h ( p a ) = 1 − p 。
那么:
F ( p a ) = p a ⋅ ( 1 − p ) F(p^a) = p^a \cdot (1 - p) F ( p a ) = p a ⋅ ( 1 − p ) 。
在代码的线性筛中:
当我们用
i i i 和质数
p p p 去筛掉
i ⋅ p i \cdot p i ⋅ p 时:
情况 A:i ( m o d p ) ∤ 0 i \pmod p \nmid 0 i ( mod p ) ∤ 0 (即 i i i 与 p p p 互质)
根据积性函数的定义,直接相乘:
F ( i ⋅ p ) = F ( i ) ⋅ F ( p ) F(i \cdot p) = F(i) \cdot F(p) F ( i ⋅ p ) = F ( i ) ⋅ F ( p )
情况 B:i ( m o d p ) = 0 i \pmod p = 0 i ( mod p ) = 0 (即 i i i 中已经包含了质因子 p p p )
设
i i i 中包含
p p p 的最高次数为
p a p^a p a ,即
i = M ⋅ p a i = M \cdot p^a i = M ⋅ p a (
M M M 与
p p p 互质)。
那么
i ⋅ p = M ⋅ p a + 1 i \cdot p = M \cdot p^{a+1} i ⋅ p = M ⋅ p a + 1 。
根据积性:
F ( i ) = F ( M ) ⋅ F ( p a ) = F ( M ) ⋅ p a ( 1 − p ) F(i) = F(M) \cdot F(p^a) = F(M) \cdot p^a (1-p) F ( i ) = F ( M ) ⋅ F ( p a ) = F ( M ) ⋅ p a ( 1 − p )
F ( i ⋅ p ) = F ( M ) ⋅ F ( p a + 1 ) = F ( M ) ⋅ p a + 1 ( 1 − p ) F(i \cdot p) = F(M) \cdot F(p^{a+1}) = F(M) \cdot p^{a+1} (1-p) F ( i ⋅ p ) = F ( M ) ⋅ F ( p a + 1 ) = F ( M ) ⋅ p a + 1 ( 1 − p )
可以发现,
F ( i ⋅ p ) F(i \cdot p) F ( i ⋅ p ) 刚好就是
F ( i ) F(i) F ( i ) 的
p p p 倍!
所以:
F ( i ⋅ p ) = F ( i ) ⋅ p F(i \cdot p) = F(i) \cdot p F ( i ⋅ p ) = F ( i ) ⋅ p 。
我们现在来分析一下优化后的复杂度。
复杂度分析
时间复杂度
线性筛预处理部分,复杂度为
O ( n ) O(n) O ( n )
在线性筛之后,有一个单层循环计算
F ( T ) F(T) F ( T ) 的前缀和,复杂度为
O ( n ) O(n) O ( n )
整除分块计算部分复杂度为
O ( n + m ) O(\sqrt{n} + \sqrt{m}) O ( n + m )
总体的复杂度还是为
O ( n ) O(n) O ( n ) ≈ 2 × 10 7 \approx 2 \times 10^7 ≈ 2 × 1 0 7
空间复杂度
加上一些零碎的全局变量,总内存占用大约在
210 M B ∼ 215 M B 210 MB \sim 215 MB 210 MB ∼ 215 MB 左右。
不过值得注意的是:
根据质数的分布范围分析,实际的 p r i m e prime p r im e 数组不用开这么大 (见下面代码)。
如果遇到题目卡常,我们可以运用
b i t s e t bitset bi t se t i s p r i m e isprime i s p r im e ,内存占用会从 10 M B 10 MB 10 MB 降到约 1.25 M B 1.25 MB 1.25 MB 。
F F F 数组在计算完前缀和之后就不再使用了。所以我们可以直接在 F F F 数组的基础上原地累加变成 s u m sum s u m 数组,从而省去一个 l o n g l o n g long long l o n g l o n g 数组的空间(省下 80 M B 80 MB 80 MB )
但是为了代码的可读性更强,我没有进行以上优化空间复杂度的操作。
以上是这道题的所有推导过程。
参考代码
CPP #include <bits/stdc++.h>
#define ll long long
using namespace std;
const int mod = 20101009 ;
const int MAX = 1e7 + 10 ;
int n, m;
bool isprime[MAX];
int prime[MAX], sub;
ll F[MAX], sum[MAX];
inline void init (int limit)
{
fill (isprime + 1 , isprime + 1 + limit, true );
F[1 ] = 1 ;
for (int i = 2 ; i <= limit; ++i)
{
if (isprime[i]) { prime[++sub] = i; F[i] = (1ll * i * (1 - i) % mod + mod) % mod; }
for (int j = 1 ; j <= sub && i * prime[j] <= limit; ++j)
{
isprime[i * prime[j]] = false ;
if (i % prime[j] == 0 ) { F[i * prime[j]] = F[i] * prime[j] % mod; break ; }
else F[i * prime[j]] = F[i] * F[prime[j]] % mod;
}
}
for (int i = 1 ; i <= limit; ++i)
{
sum[i] = (sum[i - 1 ] + F[i]) % mod;
}
}
inline ll g (ll x)
{
return x * (x + 1 ) / 2 % mod;
}
int main ()
{
scanf ("%d %d" , &n, &m);
init (max (n, m));
ll ans = 0 ;
int limit = min (n, m);
for (int l = 1 , r; l <= limit; l = r + 1 )
{
r = min (n / (n / l), m / (m / l));
ll res = (sum[r] - sum[l - 1 ] + mod) % mod;
ll val = g (n / l) * g (m / l) % mod;
ans = (ans + res * val % mod) % mod;
}
printf ("%lld\n" , ans);
return 0 ;
}
Eg4 \textit{\textbf {Eg4} } Eg4
题目简述
求
∑ i = 1 N ∑ j = 1 N l c m ( A i , A j ) \sum_{i=1}^N \sum_{j=1}^N \mathrm{lcm}(A_i, A_j) ∑ i = 1 N ∑ j = 1 N lcm ( A i , A j )
推导过程
因为
N N N 很大 (
50000 50000 50000 ),直接
O ( N 2 ) O(N^2) O ( N 2 ) 枚举肯定超时。
但是我们发现数字的大小
A i A_i A i 也很小(最大
50000 50000 50000 )。我们可以开一个桶 cnt[x],表示数字
x x x 在数组中出现了几次。于是问题转化为枚举数值
i i i 和
j j j (设
V V V 为最大数值):
∑ i = 1 V ∑ j = 1 V c n t [ i ] ⋅ c n t [ j ] ⋅ l c m ( i , j ) \sum_{i=1}^V \sum_{j=1}^V cnt[i] \cdot cnt[j] \cdot \mathrm{lcm}(i, j) ∑ i = 1 V ∑ j = 1 V c n t [ i ] ⋅ c n t [ j ] ⋅ lcm ( i , j )
把
l c m ( i , j ) \mathrm{lcm}(i, j) lcm ( i , j ) 拆成
i ⋅ j gcd ( i , j ) \frac{i \cdot j}{\gcd(i, j)} g c d ( i , j ) i ⋅ j :
∑ i = 1 V ∑ j = 1 V c n t [ i ] ⋅ c n t [ j ] ⋅ i ⋅ j gcd ( i , j ) \sum_{i=1}^V \sum_{j=1}^V cnt[i] \cdot cnt[j] \cdot \frac{i \cdot j}{\gcd(i, j)} ∑ i = 1 V ∑ j = 1 V c n t [ i ] ⋅ c n t [ j ] ⋅ g c d ( i , j ) i ⋅ j
像上面那一道题一样,我们先枚举
g = gcd ( i , j ) g = \gcd(i, j) g = g cd( i , j )
设
i = g ⋅ a , j = g ⋅ b i = g \cdot a, j = g \cdot b i = g ⋅ a , j = g ⋅ b ,此时
gcd ( a , b ) = 1 \gcd(a, b) = 1 g cd( a , b ) = 1 。
∑ g = 1 V ∑ a = 1 V / g ∑ b = 1 V / g [ gcd ( a , b ) = 1 ] ⋅ c n t [ g a ] ⋅ c n t [ g b ] ⋅ ( g a ) ⋅ ( g b ) g \sum_{g=1}^V \sum_{a=1}^{V/g} \sum_{b=1}^{V/g} [\gcd(a,b)=1] \cdot cnt[ga] \cdot cnt[gb] \cdot \frac{(ga) \cdot (gb)}{g} ∑ g = 1 V ∑ a = 1 V / g ∑ b = 1 V / g [ g cd( a , b ) = 1 ] ⋅ c n t [ g a ] ⋅ c n t [ g b ] ⋅ g ( g a ) ⋅ ( g b )
∑ g = 1 V g ∑ a = 1 V / g ∑ b = 1 V / g [ gcd ( a , b ) = 1 ] ⋅ ( c n t [ g a ] ⋅ a ) ⋅ ( c n t [ g b ] ⋅ b ) \sum_{g=1}^V g \sum_{a=1}^{V/g} \sum_{b=1}^{V/g} [\gcd(a,b)=1] \cdot (cnt[ga] \cdot a) \cdot (cnt[gb] \cdot b) ∑ g = 1 V g ∑ a = 1 V / g ∑ b = 1 V / g [ g cd( a , b ) = 1 ] ⋅ ( c n t [ g a ] ⋅ a ) ⋅ ( c n t [ g b ] ⋅ b )
这里我们发现
∑ a = 1 V / g ∑ b = 1 V / g [ gcd ( a , b ) = 1 ] \sum_{a=1}^{V/g} \sum_{b=1}^{V/g} [\gcd(a,b)=1] ∑ a = 1 V / g ∑ b = 1 V / g [ g cd( a , b ) = 1 ]
很明显可以用莫比乌斯反演进行计算,由于前面这样的计算我们已经进行了很多了,这里我们直接转换
[ gcd ( a , b ) = 1 ] = ∑ d ∣ gcd ( a , b ) μ ( d ) [\gcd(a, b) = 1] = \sum_{d|\gcd(a, b)} \mu(d) [ g cd( a , b ) = 1 ] = ∑ d ∣ g c d ( a , b ) μ ( d )
d ∣ gcd ( a , b ) d | \gcd(a, b) d ∣ g cd( a , b ) 等价于
d ∣ a d|a d ∣ a 且
d ∣ b d|b d ∣ b 。
∑ g = 1 V g ∑ a = 1 V / g ∑ b = 1 V / g ( ∑ d ∣ a , d ∣ b μ ( d ) ) ⋅ ( c n t [ g a ] ⋅ a ) ⋅ ( c n t [ g b ] ⋅ b ) \sum_{g=1}^V g \sum_{a=1}^{V/g} \sum_{b=1}^{V/g} \left( \sum_{d|a, d|b} \mu(d) \right) \cdot (cnt[ga] \cdot a) \cdot (cnt[gb] \cdot b) ∑ g = 1 V g ∑ a = 1 V / g ∑ b = 1 V / g ( ∑ d ∣ a , d ∣ b μ ( d ) ) ⋅ ( c n t [ g a ] ⋅ a ) ⋅ ( c n t [ g b ] ⋅ b )
下面又来到了关键的一步,我们同样调换枚举顺序。这里我们考虑先枚举
d d d 。 设
a = d ⋅ x a = d \cdot x a = d ⋅ x 、
b = d ⋅ x b = d \cdot x b = d ⋅ x 。
式子变为
∑ g = 1 V g ∑ d = 1 V / g μ ( d ) ∑ x = 1 ⌊ V / g d ⌋ ∑ y = 1 ⌊ V / g d ⌋ ( c n t [ g d x ] ⋅ d x ) ⋅ ( c n t [ g d y ] ⋅ d y ) \sum_{g=1}^V g \sum_{d=1}^{V/g} \mu(d) \sum_{x=1}^{\lfloor V/gd \rfloor} \sum_{y=1}^{\lfloor V/gd \rfloor} (cnt[gdx] \cdot dx) \cdot (cnt[gdy] \cdot dy) ∑ g = 1 V g ∑ d = 1 V / g μ ( d ) ∑ x = 1 ⌊ V / g d ⌋ ∑ y = 1 ⌊ V / g d ⌋ ( c n t [ g d x ] ⋅ d x ) ⋅ ( c n t [ g d y ] ⋅ d y )
这时候我们观察这个式子,可以发现
x x x 、
y y y 的枚举是独立的,也就是说,每次循环
x x x 、
y y y 的取值是相等的,所以最终的式子可以转换为
A n s = ∑ g = 1 V g ∑ d = 1 V / g μ ( d ) ⋅ ( ∑ k = 1 V / ( g d ) c n t [ k ⋅ g ⋅ d ] ⋅ k ⋅ d ) 2 Ans = \sum_{g=1}^V g \sum_{d=1}^{V/g} \mu(d) \cdot \left( \sum_{k=1}^{V/(gd)} cnt[k \cdot g \cdot d] \cdot k \cdot d \right)^2 A n s = ∑ g = 1 V g ∑ d = 1 V / g μ ( d ) ⋅ ( ∑ k = 1 V / ( g d ) c n t [ k ⋅ g ⋅ d ] ⋅ k ⋅ d ) 2
复杂度分析
时间复杂度
运用调和级数进行计算,复杂度大概是
O ( V ln V ) O(V \ln V) O ( V ln V ) 或者
O ( V ln 2 V ) O(V \ln^2 V) O ( V ln 2 V ) 级别
参考代码
CPP #include <bits/stdc++.h>
#define ll long long
using namespace std;
const int MAX = 50010 ;
int n, mxaval;
int cnt[MAX];
bool isprime[MAX];
int prime[MAX], sub;
int miu[MAX];
inline void init (int limit)
{
fill (isprime + 1 , isprime + 1 + limit, true );
miu[1 ] = 1 ;
for (int i = 2 ; i <= limit; ++i)
{
if (isprime[i]) { prime[++sub] = i; miu[i] = -1 ; }
for (int j = 1 ; j <= sub && i * prime[j] <= limit; ++j)
{
isprime[i * prime[j]] = false ;
if (i % prime[j] == 0 )
{
miu[i * prime[j]] = 0 ;
break ;
}
else miu[i * prime[j]] = -miu[i];
}
}
}
int main ()
{
scanf ("%d" , &n);
mxaval = 0 ;
for (int i = 1 ; i <= n; ++i)
{
int x; scanf ("%d" , &x);
cnt[x]++;
mxaval = max (mxaval, x);
}
init (mxaval);
ll ans = 0 ;
for (int g = 1 ; g <= mxaval; ++g)
{
ll sumd = 0 ;
for (int d = 1 ; d * g <= mxaval; ++d)
{
if (miu[d] == 0 ) continue ;
ll sumk = 0 ;
int T = d * g;
for (int k = 1 ; k * T <= mxaval; ++k)
{
if (cnt[k * T])
{
sumk += 1ll * cnt[k * T] * k * d;
}
}
if (sumk > 0 ) sumd += miu[d] * sumk * sumk;
}
ans += g * sumd;
}
printf ("%lld\n" , ans);
return 0 ;
}
Eg5 \textit{\textbf {Eg5} } Eg5
题目简述
题目要求跳蚤通过移动
∑ x i A i \sum x_i A_i ∑ x i A i (其中
A i A_i A i 是卡片上的数字,
x i x_i x i 是选了几次这个数字)加上
M M M 的若干倍(记为
y M yM y M ),最终停在
− 1 -1 − 1 的位置。即方程:
x 1 A 1 + x 2 A 2 + ⋯ + x N A N + y M = − 1 x_1 A_1 + x_2 A_2 + \dots + x_N A_N + y M = -1 x 1 A 1 + x 2 A 2 + ⋯ + x N A N + y M = − 1
要有整数解。
推导过程
根据裴蜀定理 (亦或称贝祖定理),上述线性丢番图方程有解的充要条件是:
gcd ( A 1 , A 2 , … , A N , M ) ∣ − 1 \gcd(A_1, A_2, \dots, A_N, M) \mid -1 g cd( A 1 , A 2 , … , A N , M ) ∣ − 1
因为 GCD 总是正数,所以条件等价于:
gcd ( A 1 , A 2 , … , A N , M ) = 1 \gcd(A_1, A_2, \dots, A_N, M) = 1 g cd( A 1 , A 2 , … , A N , M ) = 1
也就是我们要求有多少个序列
( A 1 , A 2 , … , A N ) (A_1, A_2, \dots, A_N) ( A 1 , A 2 , … , A N ) ,满足
1 ≤ A i ≤ M 1 \le A_i \le M 1 ≤ A i ≤ M ,且
gcd ( A 1 , … , A N , M ) = 1 \gcd(A_1, \dots, A_N, M) = 1 g cd( A 1 , … , A N , M ) = 1 。
这里我们可以发现这个答案的约束条件很强,直接求解比较难想,又看到了
gcd \gcd g cd,这时候我们可以稍微往莫比乌斯反演上面想想。
我们设目标函数
f ( d ) f(d) f ( d ) 为满足
gcd ( A 1 , … , A N , M ) = d \gcd(A_1, \dots, A_N, M) = d g cd( A 1 , … , A N , M ) = d 的卡片方案数。
我们要求的答案就是
f ( 1 ) f(1) f ( 1 ) 。
定义一个辅助函数
F ( d ) F(d) F ( d ) 为满足
d ∣ gcd ( A 1 , … , A N , M ) d \mid \gcd(A_1, \dots, A_N, M) d ∣ g cd( A 1 , … , A N , M ) 的卡片方案数。
由于
1 ≤ A i ≤ M 1 \le A_i \le M 1 ≤ A i ≤ M ,这样的数有
M / d M/d M / d 个(即
d , 2 d , … , M d d d, 2d, \dots, \frac{M}{d}d d , 2 d , … , d M d ),而我们可供选择的位置有
N N N 个,所以:
F ( d ) = ( M d ) N F(d) = \left( \frac{M}{d} \right)^N F ( d ) = ( d M ) N
根据莫比乌斯反演公式:
f ( n ) = ∑ n ∣ d μ ( d n ) F ( d ) f(n) = \sum_{n|d} \mu(\frac{d}{n}) F(d) f ( n ) = ∑ n ∣ d μ ( n d ) F ( d )
f ( 1 ) = ∑ d ∣ M μ ( d ) F ( d ) = ∑ d ∣ M μ ( d ) ( M d ) N f(1) = \sum_{d|M} \mu(d) F(d) = \sum_{d|M} \mu(d) \left( \frac{M}{d} \right)^N f ( 1 ) = ∑ d ∣ M μ ( d ) F ( d ) = ∑ d ∣ M μ ( d ) ( d M ) N
如果这里我们直接枚举
M M M 的因子,复杂度会达到
10 8 10^8 1 0 8 ,并且还要计算
μ ( d ) \mu(d) μ ( d ) ,很可能会超时。也许你会想到预处理
μ \mu μ ?但很明显这是不可取的,会直接
M L E MLE M L E ,所以我们可以考虑怎么稍微优化一下。
观察式子,我们可以发现
μ ( d ) \mu(d) μ ( d ) 只有在
d d d 没有平方质因子的时候会产生贡献 (不为0),
具体的,我们找出
M M M 的所有质因子,遍历这些质因子的所有组合,构造因子
d d d 。然后只需要看
d d d 里面包含了多少个质因子,算出此时
μ ( d ) \mu(d) μ ( d ) 的正负,计算相应的贡献即可。
说明
还有一点我想说的,要计算
1 ∼ X 1 \sim X 1 ∼ X 之间的质数个数,有一个粗略的计算公式
π ( X ) ≈ X ln X \pi(X) \approx \frac{X}{\ln X} π ( X ) ≈ l n X X
在计算时,我们可以粗略的认为
ln X ≈ 2.3 log 10 X \ln X \approx 2.3 \log_{10} X ln X ≈ 2.3 log 10 X 。
如果想要更深层的了解
π ( X ) \pi(X) π ( X ) ,可以参考
1.《初等数论》(潘承洞、 潘承彪著,北京大学出版社,第四版)、
2.《初等数论》(陈景润著,哈尔滨工业大学出版社)
3.《数论:概念和问题》(蒂图·安德雷斯库著,哈尔滨工业大学出版社)等著作,里面都有关于此不错的讲解。
此外,我们也可以通过计算质数的乘积来粗略计算。
例如对于这道题,
2 × 3 × 5 × 7 × 11 × 13 × 17 × 19 × 23 = 223 , 092 , 870 2 \times 3 \times 5 \times 7 \times 11 \times 13 \times 17 \times 19 \times 23 = 223,092,870 2 × 3 × 5 × 7 × 11 × 13 × 17 × 19 × 23 = 223 , 092 , 870 ,只需要9个从小到大的质数,就已经超过了
10 8 10^8 1 0 8 ,所以我们只需要开大小为
10 10 10 的数组就好了。
参考代码
CPP #include <bits/stdc++.h>
#define ll long long
using namespace std;
const int MAX = 50 ;
int n, m;
int prime[MAX], sub;
ll ans = 0 ;
inline ll qmi (ll a, int b)
{
ll res = 1 ;
while (b)
{
if (b & 1 ) res = res * a;
a = a * a;
b >>= 1 ;
}
return res;
}
void dfs (int idx, int d, int cnt)
{
if (idx > sub)
{
ll term = qmi ((ll)m / d, n);
ans += (cnt % 2 == 0 ? term : -term);
return ;
}
dfs (idx + 1 , d, cnt); dfs (idx + 1 , d * prime[idx], cnt + 1 );
}
int main ()
{
scanf ("%d %d" , &n, &m);
int temp = m;
for (int i = 2 ; i * i <= temp; ++i)
{
if (temp % i == 0 )
{
prime[++sub] = i;
while (temp % i == 0 ) temp /= i;
}
}
if (temp > 1 ) prime[++sub] = temp;
dfs (1 , 1 , 0 );
printf ("%lld\n" , ans);
return 0 ;
}
Eg6 \textit{\textbf {Eg6} } Eg6
题目简述
求
A n s Ans A n s 对
2 13 2^{13} 2 13 取模的结果。其中,
Ans = ∑ i = 1 n ∑ j = 1 m [ f ( gcd ( i , j ) ) ≤ a ] ⋅ f ( gcd ( i , j ) ) \text{Ans} = \sum_{i=1}^n \sum_{j=1}^m [f(\gcd(i, j)) \le a] \cdot f(\gcd(i, j)) Ans = ∑ i = 1 n ∑ j = 1 m [ f ( g cd( i , j )) ≤ a ] ⋅ f ( g cd( i , j ))
推导过程
忽略
f ( gcd ( i , j ) ) ≤ a f(\gcd(i, j)) \le a f ( g cd( i , j )) ≤ a 的限制,先推导通项公式。
令
d = gcd ( i , j ) d = \gcd(i, j) d = g cd( i , j ) ,枚举
d d d :
∑ d = 1 min ( n , m ) f ( d ) ∑ i = 1 n ∑ j = 1 m [ gcd ( i , j ) = d ] \sum_{d=1}^{\min(n, m)} f(d) \sum_{i=1}^n \sum_{j=1}^m [\gcd(i, j) = d] ∑ d = 1 m i n ( n , m ) f ( d ) ∑ i = 1 n ∑ j = 1 m [ g cd( i , j ) = d ]
∑ d = 1 min ( n , m ) f ( d ) ∑ i = 1 ⌊ n / d ⌋ ∑ j = 1 ⌊ m / d ⌋ [ gcd ( i , j ) = 1 ] \sum_{d=1}^{\min(n, m)} f(d) \sum_{i=1}^{\lfloor n/d \rfloor} \sum_{j=1}^{\lfloor m/d \rfloor} [\gcd(i, j) = 1] ∑ d = 1 m i n ( n , m ) f ( d ) ∑ i = 1 ⌊ n / d ⌋ ∑ j = 1 ⌊ m / d ⌋ [ g cd( i , j ) = 1 ]
利用莫比乌斯反演,
∑ d = 1 min ( n , m ) f ( d ) ∑ k = 1 min ( ⌊ n / d ⌋ , ⌊ m / d ⌋ ) μ ( k ) ⌊ n d k ⌋ ⌊ m d k ⌋ \sum_{d=1}^{\min(n, m)} f(d) \sum_{k=1}^{\min(\lfloor n/d \rfloor, \lfloor m/d \rfloor)} \mu(k) \lfloor \frac{n}{dk} \rfloor \lfloor \frac{m}{dk} \rfloor ∑ d = 1 m i n ( n , m ) f ( d ) ∑ k = 1 m i n (⌊ n / d ⌋ , ⌊ m / d ⌋) μ ( k ) ⌊ d k n ⌋ ⌊ d k m ⌋
令
T = d ⋅ k T = d \cdot k T = d ⋅ k ,则
d d d 是
T T T 的约数。我们要枚举
T T T :
∑ T = 1 min ( n , m ) ⌊ n T ⌋ ⌊ m T ⌋ ∑ d ∣ T f ( d ) ⋅ μ ( T d ) \sum_{T=1}^{\min(n, m)} \lfloor \frac{n}{T} \rfloor \lfloor \frac{m}{T} \rfloor \sum_{d|T} f(d) \cdot \mu(\frac{T}{d}) ∑ T = 1 m i n ( n , m ) ⌊ T n ⌋ ⌊ T m ⌋ ∑ d ∣ T f ( d ) ⋅ μ ( d T )
现在考虑限制条件
f ( d ) ≤ a f(d) \le a f ( d ) ≤ a 。
公式变为:
Ans = ∑ T = 1 min ( n , m ) ⌊ n T ⌋ ⌊ m T ⌋ ( ∑ d ∣ T , f ( d ) ≤ a f ( d ) ⋅ μ ( T d ) ) \text{Ans} = \sum_{T=1}^{\min(n, m)} \lfloor \frac{n}{T} \rfloor \lfloor \frac{m}{T} \rfloor \left( \sum_{d|T, f(d) \le a} f(d) \cdot \mu(\frac{T}{d}) \right) Ans = ∑ T = 1 m i n ( n , m ) ⌊ T n ⌋ ⌊ T m ⌋ ( ∑ d ∣ T , f ( d ) ≤ a f ( d ) ⋅ μ ( d T ) )
内层的求和
∑ d ∣ T , f ( d ) ≤ a f ( d ) ⋅ μ ( T d ) \sum_{d|T, f(d) \le a} f(d) \cdot \mu(\frac{T}{d}) ∑ d ∣ T , f ( d ) ≤ a f ( d ) ⋅ μ ( d T ) 依赖于
a a a 。由于有多组查询,且
a a a 各不相同,我们不能针对每个
a a a 重新计算。
我们可以考虑用 离线 + 树状数组 解决
排序:将所有查询按
a a a 从小到大排序。同时,将所有数字
1 … 10 5 1 \dots 10^5 1 … 1 0 5 按照其约数和
f ( d ) f(d) f ( d ) 从小到大排序。
动态更新:遍历排序后的查询。对于当前的查询限制
a a a ,将所有满足
f ( d ) ≤ a f(d) \le a f ( d ) ≤ a 的数字
d d d 加入到数据结构中。
当我们加入一个数字 d d d 时,它会对所有 T T T (其中 T T T 是 d d d 的倍数) 产生贡献。
贡献值为 f ( d ) ⋅ μ ( T / d ) f(d) \cdot \mu(T/d) f ( d ) ⋅ μ ( T / d ) 。
我们需要一个支持单点修改、区间求和的数据结构来维护 g ( T ) = ∑ f ( d ) μ ( T / d ) g(T) = \sum f(d) \mu(T/d) g ( T ) = ∑ f ( d ) μ ( T / d ) 的前缀和。毫无疑问,树状数组是最佳选择。
整除分块:对于每个查询,利用维护好的树状数组,结合 ⌊ n / T ⌋ \lfloor n/T \rfloor ⌊ n / T ⌋ 和 ⌊ m / T ⌋ \lfloor m/T \rfloor ⌊ m / T ⌋ 的整除分块(数论分块)在 O ( n log n ) O(\sqrt{n} \log n) O ( n log n ) 时间内算出答案。
复杂度
时间复杂度
线性筛预处理 μ \mu μ 和 f f f (即 σ 1 \sigma_1 σ 1 ):O ( N ) O(N) O ( N ) 。
排序:O ( N log N + Q log Q ) O(N \log N + Q \log Q) O ( N log N + Q log Q ) 。
离线处理 + 树状数组更新:每个数 d d d 会更新其所有倍数,总复杂度 ∑ N d = O ( N log N ) \sum \frac{N}{d} = O(N \log N) ∑ d N = O ( N log N ) 。
查询:每个查询 O ( N ⋅ log N ) O(\sqrt{N} \cdot \log N) O ( N ⋅ log N ) 。
总复杂度:O ( N log N + Q N log N ) O(N \log N + Q\sqrt{N} \log N) O ( N log N + Q N log N ) ,通过这道题绰绰有余
参考代码
CPP #include <bits/stdc++.h>
#define ll long long
using namespace std;
const ll MOD = 1 << 31 ;
const int MAX = 1e5 + 10 ;
int Q;
int prime[MAX], sub;
bool isprime[MAX];
int miu[MAX];
int sigma[MAX];
int low[MAX];
int smallp[MAX];
int bit[MAX];
struct node
{
int id, val;
}f[MAX];
struct ASK
{
int n, m, a, id;
}q[MAX];
int ans[MAX];
inline bool compare1 (node x, node y)
{
return x.val < y.val;
}
inline bool compare2 (ASK x, ASK y)
{
return x.a < y.a;
}
inline void init (int limit)
{
fill (isprime + 1 , isprime + 1 + limit, true );
miu[1 ] = 1 ; sigma[1 ] = 1 ;
for (int i = 2 ; i <= limit; ++i)
{
if (isprime[i])
{
prime[++sub] = i;
miu[i] = -1 ;
sigma[i] = i + 1 ;
low[i] = i + 1 ;
smallp[i] = i;
}
for (int j = 1 ; j <= sub && i * prime[j] <= limit; ++j)
{
isprime[i * prime[j]] = false ;
if (i % prime[j] == 0 )
{
miu[i * prime[j]] = 0 ;
smallp[i * prime[j]] = smallp[i] * prime[j];
low[i * prime[j]] = low[i] * prime[j] + 1 ;
sigma[i * prime[j]] = sigma[i / smallp[i]] * low[i * prime[j]];
break ;
}
else
{
miu[i * prime[j]] = -miu[i];
smallp[i * prime[j]] = prime[j];
low[i * prime[j]] = prime[j] + 1 ;
sigma[i * prime[j]] = sigma[i] * sigma[prime[j]];
}
}
}
for (int i = 1 ; i <= limit; ++i) f[i].id = i, f[i].val = sigma[i];
}
inline int lowbit (int x)
{
return x & -x;
}
inline void add (int x, int val)
{
for (; x < MAX; x += lowbit (x)) bit[x] += val;
}
inline int query (int x)
{
int res = 0 ;
for (; x > 0 ; x -= lowbit (x)) res += bit[x];
return res;
}
int main ()
{
ios::sync_with_stdio (false );
cin.tie (0 ); cout.tie (0 );
cin >> Q;
init (100000 );
for (int i = 1 ; i <= Q; ++i)
{
cin >> q[i].n >> q[i].m >> q[i].a;
q[i].id = i;
}
sort (f + 1 , f + 100001 , compare1);
sort (q + 1 , q + 1 + Q, compare2);
int pos = 1 ;
for (int i = 1 ; i <= Q; ++i)
{
while (pos <= 100000 && f[pos].val <= q[i].a)
{
int d = f[pos].id;
for (int T = d; T <= 100000 ; T += d)
{
add (T, f[pos].val * miu[T / d]);
}
pos++;
}
int n = q[i].n, m = q[i].m;
if (n > m) swap (n, m);
ll res = 0 ;
for (int l = 1 , r; l <= n; l = r + 1 )
{
r = min (n / (n / l), m / (m / l));
res += (n / l) * (m / l) % MOD * (query (r) - query (l - 1 )) % MOD;
}
ans[q[i].id] = res % MOD;
}
for (int i = 1 ; i <= Q; ++i) cout << ans[i] << endl;
return 0 ;
}
说明
代码中的
l o w [ M A X ] low[MAX] l o w [ M A X ] 存的是数字
i i i 的最小质因子
p p p 的等比数列之和。
i = p 1 c 1 ⋅ p 2 c 2 ⋯ p k c k i = p_1^{c_1} \cdot p_2^{c_2} \cdots p_k^{c_k} i = p 1 c 1 ⋅ p 2 c 2 ⋯ p k c k
假设
p 1 p_1 p 1 是最小的质因子,那么 low[i] 存的就是:
low [ i ] = 1 + p 1 + p 1 2 + ⋯ + p 1 c 1 \text{low}[i] = 1 + p_1 + p_1^2 + \dots + p_1^{c_1} low [ i ] = 1 + p 1 + p 1 2 + ⋯ + p 1 c 1
可以利用这个算
数和函数 σ ( i ) \sigma(i) σ ( i ) 。
结语
以上就是
莫比乌斯反演 的全部讲解啦,到这里你应该对莫比乌斯反演这个神奇的工具有了一定的了解了。最后给大家推荐一个自己建的题单:
莫比乌斯反演的运用。里面有一些关于莫比乌斯反演的好题,还是值得一做的(保证可以用莫比乌斯反演完成)。
感谢大家耐心看完,希望这篇文章对你有所帮助。
完结撒花 QWQ