杜教筛
本文参考pengym的杜教筛
杜教筛需要一些预备知识:数论分块(可以参考之前的文章ACM数论初步)、积性函数、狄利克雷卷积,最好了解莫比乌斯函数
积性函数
对于数论函数 f ( x ) f(x) f(x),如果对于任意满足 gcd ( x , y ) = 1 \gcd(x,y)=1 gcd(x,y)=1的 x , y x,y x,y,有 f ( x ⋅ y ) = f ( x ) ⋅ f ( y ) f(x\cdot y)=f(x)\cdot f(y) f(x⋅y)=f(x)⋅f(y),则称 f ( x ) f(x) f(x)为积性函数;如果去掉互质的条件仍能满足后面的性质成立,则称 f ( x ) f(x) f(x)为完全积性函数
几个常见的积性函数:
- μ ( n ) \mu(n) μ(n)——莫比乌斯函数,在莫比乌斯反演讲过了
- φ ( n ) \varphi(n) φ(n)——欧拉函数,表示 [ 1 , n − 1 ] [1,n-1] [1,n−1]中与 n n n互质的数的个数( [ 1 , n ] [1,n] [1,n]也一样的,反正 n n n和 n n n也不互质)
- d ( n ) d(n) d(n)——约数个数函数,表示 n n n的约数个数, d ( n ) = ∑ d ∣ n 1 = ∑ d = 1 n [ d ∣ n ] d(n)=\sum\limits_{d|n}1=\sum\limits_{d=1}^{n}[d|n] d(n)=d∣n∑1=d=1∑n[d∣n]
- σ ( n ) \sigma(n) σ(n)——约数和函数,表示 n n n的各个约数之和, σ ( n ) = ∑ d ∣ n d = ∑ d = 1 n [ d ∣ n ] ∗ d \sigma(n)=\sum\limits_{d|n}d=\sum\limits_{d=1}^{n}[d|n]*d σ(n)=d∣n∑d=d=1∑n[d∣n]∗d
几个常见的完全积性函数:
- ϵ ( n ) \epsilon(n) ϵ(n)——元函数,只有 n = 1 n=1 n=1时函数值为1,其他情况为0, ϵ ( n ) = [ n = = 1 ] \epsilon(n)=[n==1] ϵ(n)=[n==1]
- I ( n ) I(n) I(n)——恒等函数,函数值恒为1
- i d ( n ) id(n) id(n)——单位函数, i d ( n ) = n id(n)=n id(n)=n
积性函数还有一个重要的性质:积性函数 ∗ * ∗积性函数仍为积性函数,这个性质利用积性函数的定义可以很容易地推出
狄利克雷卷积
定义
两个数论函数 f f f和 g g g的卷积为 ( f ∗ g ) ( n ) = ∑ d ∣ n f ( d ) ⋅ g ( n d ) (f*g)(n) = \sum\limits_{d|n}f(d) \cdot g(\dfrac{n}{d}) (f∗g)(n)=d∣n∑f(d)⋅g(dn),读作 f f f卷 g g g,其实读 g g g卷 f f f也是一样的,因为有定义很容易看出狄利克雷卷积有交换律(本文中的 ′ ∗ ′ '*' ′∗′均为狄利克雷卷积, ′ ⋅ ′ '\cdot' ′⋅′为通常的乘法)
性质
- 交换律: f ∗ g = g ∗ f f*g=g*f f∗g=g∗f
- 结合律: ( f ∗ g ) ∗ h = f ∗ ( g ∗ h ) (f*g)*h=f*(g*h) (f∗g)∗h=f∗(g∗h)
- 分配律: ( f + g ) ∗ h = f ∗ h + g ∗ h (f+g)*h=f*h+g*h (f+g)∗h=f∗h+g∗h,严格来讲是卷积运算对 '+'运算满足分配律
读者们看到 ϵ ( n ) \epsilon(n) ϵ(n)可能觉得这种函数啥用?从近世代数的角度看,记所有数论函数的集合为 S S S, S S S和狄利克雷卷积 ∗ * ∗就构成了一个代数系 ( S , ∗ ) (S,*) (S,∗),而 ϵ ( n ) \epsilon(n) ϵ(n)就是这个代数系的单位元,由于狄利克雷卷积 ∗ * ∗满足结合律和交换律,所以 ( S , ∗ ) (S,*) (S,∗)是个可换半群,再加上单位元, ( S , ∗ , ϵ ) (S,*,\epsilon) (S,∗,ϵ)就是一个可换幺半群,至于它是否为一个群在此不作讨论(近世代数的内容看不懂也没关系,不影响后面的内容,笔者也只是刚学一点,而且学得极烂)
狄利克雷卷积可以用来证明一些性质,例如:
-
莫比乌斯函数 μ \mu μ
莫比乌斯函数有一个重要性质: ∑ d ∣ n μ ( d ) = [ n = = 1 ] \sum\limits_{d|n}\mu(d)=[n==1] d∣n∑μ(d)=[n==1],下面我们用狄利克雷卷积证明莫比乌斯反演:
由已知得
F ( n ) = ∑ d ∣ n f ( d ) , F(n) = \sum\limits_{d|n}f(d), F(n)=d∣n∑f(d), 等价于 F = f ∗ I F=f*I F=f∗I,两边同时卷积莫比乌斯函数 μ \mu μ,得
F ∗ μ = ( f ∗ I ) ∗ μ F*\mu=(f*I)*\mu F∗μ=(f∗I)∗μ 由结合律得
F ∗ μ = ( f ∗ I ) ∗ μ = f ∗ ( I ∗ μ ) = f ∗ ∑ d ∣ n I ( d ) ⋅ μ ( n d ) = f ∗ ∑ d ∣ n μ ( n d ) {F*\mu\\ =(f*I)*\mu\\ =f*(I*\mu)\\ =f*\sum_{d|n}I(d)\cdot \mu(\dfrac{n}{d})\\ =f*\sum_{d|n}\mu(\dfrac{n}{d}) } F∗μ=(f∗I)∗μ=f∗(I∗μ)=f∗d∣n∑I(d)⋅μ(dn)=f∗d∣n∑μ(dn) 再根据 μ \mu μ的性质 ∑ d ∣ n μ ( d ) = [ n = = 1 ] \sum\limits_{d|n}\mu(d)=[n==1] d∣n∑μ(d)=[n==1],上式又化为 f ∗ ϵ f*\epsilon f∗ϵ,就等于 f f f,所以就有
f = F ∗ μ = ∑ d ∣ n F ( d ) ⋅ μ ( n d ) f=F*\mu=\sum_{d|n}F(d)\cdot\mu(\dfrac{n}{d}) f=F∗μ=d∣n∑F(d)⋅μ(dn)
采用狄利克雷卷积的证明简单易懂,比之前的和式变换直观许多
-
欧拉函数 φ \varphi φ
欧拉函数有一个重要的性质: ∑ d ∣ n φ ( d ) = n \sum\limits_{d|n}\varphi(d) = n d∣n∑φ(d)=n,用它和狄利克雷卷积,我们可以证明莫比乌斯函数的一个性质: φ ( n ) n = ∑ d ∣ n μ ( d ) d \dfrac{\varphi(n)}{n}=\sum\limits_{d|n}\dfrac{\mu(d)}{d} nφ(n)=d∣n∑dμ(d)
由已知
∑ d ∣ n φ ( d ) = n \sum_{d|n}\varphi(d) = n d∣n∑φ(d)=n 将它表示为狄利克雷卷积形式: φ ∗ I = i d \varphi*I=id φ∗I=id,式子两边同时卷 μ \mu μ,得
φ ∗ I ∗ μ = i d ∗ μ ⟺ φ ∗ ϵ = i d ∗ μ ⟺ φ = i d ∗ μ \varphi*I*\mu=id*\mu\\ \iff \varphi*\epsilon=id*\mu\\ \iff \varphi=id*\mu φ∗I∗μ=id∗μ⟺φ∗ϵ=id∗μ⟺φ=id∗μ 所以 φ ( n ) = ∑ d ∣ n μ ( d ) ⋅ n d \varphi(n)=\sum\limits_{d|n}\mu(d)\cdot\dfrac{n}{d} φ(n)=d∣n∑μ(d)⋅dn,移项得
φ ( n ) n = ∑ d ∣ n μ ( d ) d \dfrac{\varphi(n)}{n} = \sum_{d|n} \dfrac{\mu(d)}{d} nφ(n)=d∣n∑dμ(d)
杜教筛
掌握了前面的知识以后,就可以开始杜教筛的学习了。首先,我们应该清楚一个问题:杜教筛到底是用来干什么的?杜教筛是以低于线性时间复杂度计算出积性函数前缀和的筛法,需要注意的是它筛的不是 [ 1 , n ] [1, n] [1,n]这么大的范围,它只筛 n n n,不过能在筛 n n n能顺路筛出一些其他的(具体看下文吧
也就是说我们要求 ∑ i = 1 n f ( i ) \sum\limits_{i=1}^n f(i) i=1∑nf(i),其中 f ( i ) f(i) f(i)是积性函数,我们姑且记 ∑ i = 1 n f ( i ) = S ( n ) \sum\limits_{i=1}^nf(i)=S(n) i=1∑nf(i)=S(n)
为了解决上面的问题,我们构造两个积性函数
h
h
h和
g
g
g,使得
h
=
f
∗
g
h=f*g
h=f∗g,下面我们开始求
∑
i
=
1
n
h
(
i
)
\sum\limits_{i=1}^n h(i)
i=1∑nh(i)
∑
i
=
1
n
h
(
i
)
=
∑
i
=
1
n
∑
d
∣
i
g
(
d
)
⋅
f
(
i
d
)
=
∑
d
=
1
n
g
(
d
)
⋅
∑
i
=
1
⌊
n
d
⌋
f
(
i
)
=
∑
d
=
1
n
g
(
d
)
⋅
S
(
⌊
n
d
⌋
)
\begin{aligned} \sum_{i=1}^n h(i) & =\sum_{i=1}^n\sum_{d|i}g(d)\cdot f(\dfrac{i}{d})\\ & = \sum_{d=1}^n g(d)\cdot \sum_{i=1}^{\left\lfloor \tfrac{n}{d}\right\rfloor} f(i)\\ & = \sum_{d=1}^ng(d)\cdot S(\left\lfloor \frac{n}{d} \right\rfloor) \end{aligned}
i=1∑nh(i)=i=1∑nd∣i∑g(d)⋅f(di)=d=1∑ng(d)⋅i=1∑⌊dn⌋f(i)=d=1∑ng(d)⋅S(⌊dn⌋)接着,将和式的第一项提出来,得到
g
(
1
)
⋅
S
(
n
)
=
∑
i
=
1
n
h
(
i
)
−
∑
d
=
2
n
g
(
d
)
⋅
S
(
⌊
n
d
⌋
)
g(1)\cdot S(n)=\sum_{i=1}^n h(i) - \sum_{d=2}^n g(d)\cdot S(\left\lfloor \frac{n}{d} \right\rfloor)
g(1)⋅S(n)=i=1∑nh(i)−d=2∑ng(d)⋅S(⌊dn⌋)这样,如果
h
(
i
)
h(i)
h(i)前缀和能很快地求出,对后面的式子利用分块(不过
g
(
d
)
g(d)
g(d)也要能快速求和,且
d
d
d从2开始,不是从1开始),可以将求
S
(
n
)
S(n)
S(n)的复杂度降到
O
(
n
2
3
)
O(n^{\frac{2}{3}})
O(n32)。杜教筛的套路就是找到一个
g
g
g,使得
f
∗
g
=
h
f*g=h
f∗g=h的前缀和能简单求出,顺便
g
g
g的求和也简单。注意,杜教筛不像线性筛,比如线性筛筛
φ
、
μ
\varphi、\mu
φ、μ这些函数一遍筛就把
[
1
,
n
]
[1,n]
[1,n]的都筛出来,而杜教筛是只筛出
S
(
n
)
S(n)
S(n),不过从上面的式子很容易就想到递归求解的方式,可以把递归时求出的一些
S
(
⌊
n
d
⌋
)
S(\left\lfloor\dfrac{n}{d}\right\rfloor)
S(⌊dn⌋)保存下来,这样可以筛得快一些(因为数组没法开到1e9那种级别,可以用个map来存筛出来的
S
S
S)。一般前几项(比如说前1e6项)直接按照线性筛的筛法筛出来(积性函数有个性质——能被
O
(
n
)
O(n)
O(n)筛出来),然后
O
(
n
)
O(n)
O(n)求一遍前缀和来算;后面的项用上面推导出来的式子,拿杜教筛筛
来点例子:
-
求 S ( n ) = ∑ i = 1 n μ ( i ) S(n)=\sum\limits_{i=1}^n \mu(i) S(n)=i=1∑nμ(i)
这里的 f = μ f=\mu f=μ,现在的目标是找一个 g g g,使得 f f f卷 g g g得到的函数很好求前缀和,顺便 g g g也要比较好求和,从上文我们已经得到一个结论: μ ∗ I = ϵ \mu*I=\epsilon μ∗I=ϵ,而 ϵ \epsilon ϵ的前缀和很好求,就是1,所以有
S ( n ) = 1 − ∑ d = 2 n S ( ⌊ n d ⌋ ) S(n)=1-\sum_{d=2}^n S(\left\lfloor\frac{n}{d}\right\rfloor) S(n)=1−d=2∑nS(⌊dn⌋) -
求 S ( n ) = ∑ i = 1 n φ ( i ) S(n)=\sum\limits_{i=1}^{n}\varphi(i) S(n)=i=1∑nφ(i)
由欧拉函数的性质,我们知道 ( φ ∗ I ) ( n ) = n (\varphi*I)(n)=n (φ∗I)(n)=n,也就是 φ ∗ I = i d \varphi*I=id φ∗I=id, i d id id求和很简单,所以有
S ( n ) = ∑ i = 1 n i − ∑ d = 2 n S ( ⌊ n d ⌋ ) S(n)=\sum_{i=1}^n i-\sum_{d=2}^n S(\left\lfloor\frac{n}{d}\right\rfloor) S(n)=i=1∑ni−d=2∑nS(⌊dn⌋) -
求 S ( n ) = ∑ i = 1 n i ⋅ φ ( i ) S(n)=\sum\limits_{i=1}^n i\cdot \varphi(i) S(n)=i=1∑ni⋅φ(i)
这个式子乍一看难找 g g g来和 f f f卷,这时候我们就回到狄利克雷卷积的定义,我们要找的是 h = f ∗ g h=f*g h=f∗g使得 h h h的前缀和比较好求,而 ( f ∗ g ) ( n ) = ∑ d ∣ n ( d ⋅ φ ( d ) ) ⋅ g ( n d ) (f*g)(n) = \sum\limits_{d|n}(d\cdot \varphi(d))\cdot g(\dfrac{n}{d}) (f∗g)(n)=d∣n∑(d⋅φ(d))⋅g(dn)。现在的问题就是前面的 d d d在这不好求,如果用 g g g能约掉就好了,我们发现 g ( n d ) g(\dfrac{n}{d}) g(dn)的分母有个 d d d,那么令 g = i d g=id g=id,那么 h ( n ) = ∑ d ∣ n φ ( d ) ⋅ n = n ⋅ ∑ d ∣ n φ ( d ) = n 2 h(n)=\sum\limits_{d|n}\varphi(d)\cdot n=n\cdot\sum\limits_{d|n} \varphi(d)=n^2 h(n)=d∣n∑φ(d)⋅n=n⋅d∣n∑φ(d)=n2,所以有
S ( n ) = ∑ i = 1 n i 2 − ∑ d = 2 n d ⋅ S ( ⌊ n d ⌋ ) S(n)=\sum_{i=1}^n i^2-\sum_{d=2}^nd\cdot S(\left\lfloor \frac{n}{d} \right\rfloor) S(n)=i=1∑ni2−d=2∑nd⋅S(⌊dn⌋) 再根据 ∑ i = 1 n i 2 = n ( n + 1 ) ( 2 n + 1 ) 6 \sum\limits_{i=1}^n i^2=\dfrac{n(n+1)(2n+1)}{6} i=1∑ni2=6n(n+1)(2n+1),就能筛了
板子
-
#include <cstdio> #include <map> #define N 5000010 typedef long long ll; bool check[N]; int mu[N], sum_mu[N], phi[N], prime[N >> 2], tot; ll sum_phi[N]; std::map<int, int> map_mu; std::map<int, ll> map_phi; void get(int n) { phi[1] = mu[1]= 1; check[1] = true; for(int i = 2; i <= n; i++) { if(!check[i]) prime[++tot] = i, mu[i] = -1, phi[i] = i - 1; for(int j = 1; j <= tot && i * prime[j] <= n; j++) { check[i * prime[j]] = true; if(i % prime[j] == 0) { mu[i * prime[j]] = 0; phi[i * prime[j]] = phi[i] * prime[j]; break; } else { mu[i * prime[j]] = -mu[i]; phi[i * prime[j]] = phi[i] * (prime[j] - 1); } } } for(int i = 1; i <= n; i++) sum_mu[i] = sum_mu[i - 1] + mu[i], sum_phi[i] = sum_phi[i - 1] + phi[i]; } int djmu(int n) { if(n < N) return sum_mu[n]; if(map_mu.count(n)) return map_mu[n]; int ans = 1; for(int l = 2, r = 0; l <= n; l = r + 1) { r = n / (n / l); ans -= (r - l + 1) * djmu(n / l); } map_mu.insert(std::make_pair(n, ans)); return ans; } ll djphi(int n) { if(n < N) return sum_phi[n]; if(map_phi.count(n)) return map_phi[n]; ll ans = (ll)(n + 1) * n >> 1; for(int l = 2, r = 0; l <= n; l = r + 1) { r = n / (n / l); ans -= (ll)(r - l + 1) * djphi(n / l); } map_phi.insert(std::make_pair(n, ans)); return ans; } int main() { get(N - 1); int t; scanf("%d", &t); while(t--) { int n; scanf("%d", &n); printf("%lld %d\n", djphi(n), djmu(n)); } return 0; }