接着 Dirichlet 卷积,继续学习杜教筛。
本文中一些函数的定义见此博文
通过杜教筛,我们可以快速求出某一数论函数 f f f 的前缀和,即,我们可以在低于线性的时间复杂度内求出 S ( n ) = ∑ i = 1 n f ( i ) S(n)=\sum\limits_{i=1}^n f(i) S(n)=i=1∑nf(i)
杜教筛主要运用一个公式,通过这个公式我们建立了 S ( n ) S(n) S(n) 与 S ( n i ) S(\frac{n}{i}) S(in) 的关系式,已知两个数论函数 f , g f,g f,g, S ( n ) = ∑ i = 1 n f ( i ) S(n)=\sum\limits_{i=1}^n f(i) S(n)=i=1∑nf(i),则有:
∑ i = 1 n ( f ∗ g ) ( i ) = ∑ i = 1 n g ( i ) ⋅ S ( ⌊ n i ⌋ ) \sum\limits_{i=1}^n (f\ast g)(i)=\sum\limits_{i=1}^n g(i)\cdot S(\lfloor\frac{n}{i}\rfloor) i=1∑n(f∗g)(i)=i=1∑ng(i)⋅S(⌊in⌋)
我们给个证明:
∑ i = 1 n ( f ∗ g ) ( i ) = ∑ i = 1 n ∑ d ∣ i f ( d ) ⋅ g ( i d ) = ∑ d = 1 n ∑ i = 1 ⌊ n d ⌋ g ( d ) ⋅ f ( i ) = ∑ d = 1 n g ( d ) ⋅ ∑ i = 1 ⌊ n d ⌋ f ( i ) = ∑ d = 1 n g ( d ) ⋅ S ( ⌊ n d ⌋ ) = ∑ i = 1 n g ( i ) ⋅ S ( ⌊ n i ⌋ ) □ \begin{aligned} \sum\limits_{i=1}^n (f\ast g)(i) \\=\sum\limits_{i=1}^n\sum\limits_{d\mid i} f(d)\cdot g(\frac{i}{d})\\=\sum\limits_{d=1}^n\sum\limits_{i=1}^{\lfloor \frac{n}{d}\rfloor} g(d)\cdot f(i)\\=\sum\limits_{d=1}^n g(d)\cdot\sum\limits_{i=1}^{\lfloor \frac{n}{d}\rfloor} f(i)\\=\sum\limits_{d=1}^n g(d)\cdot S(\lfloor \frac{n}{d}\rfloor)\\=\sum\limits_{i=1}^n g(i)\cdot S(\lfloor \frac{n}{i}\rfloor)\\&\square \end{aligned} i=1∑n(f∗g)(i)=i=1∑nd∣i∑f(d)⋅g(di)=d=1∑ni=1∑⌊dn⌋g(d)⋅f(i)=d=1∑ng(d)⋅i=1∑⌊dn⌋f(i)=d=1∑ng(d)⋅S(⌊dn⌋)=i=1∑ng(i)⋅S(⌊in⌋)□
证明的关键就在于中间对于 d , i d,i d,i 枚举顺序的变化,这个证明值得细品。
那么我们有了这个公式有什么用呢?我们再来导一下:
∑ i = 1 n ( f ∗ g ) ( i ) = ∑ i = 1 n g ( i ) ⋅ S ( ⌊ n i ⌋ ) ∑ i = 1 n ( f ∗ g ) ( i ) = g ( 1 ) ⋅ S ( n ) + ∑ i = 2 n g ( i ) ⋅ S ( ⌊ n i ⌋ ) g ( 1 ) ⋅ S ( n ) = ∑ i = 1 n ( f ∗ g ) ( i ) − ∑ i = 2 n g ( i ) ⋅ S ( ⌊ n i ⌋ ) \begin{aligned} \sum\limits_{i=1}^n (f\ast g)(i)=\sum\limits_{i=1}^n g(i)\cdot S(\lfloor\frac{n}{i}\rfloor)\\\sum\limits_{i=1}^n (f\ast g)(i)=g(1)\cdot S(n)+\sum\limits_{i=2}^n g(i)\cdot S(\lfloor\frac{n}{i}\rfloor)\\g(1)\cdot S(n)=\sum\limits_{i=1}^n (f\ast g)(i)-\sum\limits_{i=2}^n g(i)\cdot S(\lfloor\frac{n}{i}\rfloor) \end{aligned} i=1∑n(f∗g)(i)=i=1∑ng(i)⋅S(⌊in⌋)i=1∑n(f∗g)(i)=g(1)⋅S(n)+i=2∑ng(i)⋅S(⌊in⌋)g(1)⋅S(n)=i=1∑n(f∗g)(i)−i=2∑ng(i)⋅S(⌊in⌋)
我们可以轻松地求出 S ( n ) S(n) S(n) 的值。而 S ( ⌊ n i ⌋ ) S(\lfloor\frac{n}{i}\rfloor) S(⌊in⌋) 可以通过数论分块来解决。由此看来,杜教筛的时间复杂度为低于线性复杂度。
看了理论,可能还有点晕,我们来看几个例子。
令 S ( n ) = ∑ i = 1 n μ ( i ) ( n < 2 31 ) S(n)=\sum\limits_{i=1}^n \mu(i)\ \ \ \ (n<2^{31}) S(n)=i=1∑nμ(i) (n<231)
我们根据公式来求,我们令 f = μ , g = 1 f=\mu,g=1 f=μ,g=1,由 Dirichlet 卷积知识,我们知道 μ ∗ 1 = ε \mu\ast 1=\varepsilon μ∗1=ε。那么:
g ( 1 ) ⋅ S ( n ) = ∑ i = 1 n ( f ∗ g ) ( i ) − ∑ i = 2 n g ( i ) ⋅ S ( ⌊ n i ⌋ ) 1 ⋅ S ( n ) = ∑ i = 1 n ε ( i ) − ∑ i = 2 n 1 ⋅ S ( ⌊ n i ⌋ ) S ( n ) = 1 − ∑ i = 2 n S ( ⌊ n i ⌋ ) \begin{aligned} g(1)\cdot S(n)=\sum\limits_{i=1}^n (f\ast g)(i)-\sum\limits_{i=2}^n g(i)\cdot S(\lfloor\frac{n}{i}\rfloor)\\1\cdot S(n)=\sum\limits_{i=1}^n \varepsilon(i)-\sum\limits_{i=2}^n 1\cdot S(\lfloor\frac{n}{i}\rfloor)\\S(n)=1-\sum\limits_{i=2}^n S(\lfloor\frac{n}{i}\rfloor) \end{aligned} g(1)⋅S(n)=i=1∑n(f∗g)(i)−i=2∑ng(i)⋅S(⌊in⌋)1⋅S(n)=i=1∑nε(i)−i=2∑n1⋅S(⌊in⌋)S(n)=1−i=2∑nS(⌊in⌋)
根据数论分块的知识, ⌊ n i ⌋ \lfloor\frac{n}{i}\rfloor ⌊in⌋ 的取值有 O ( n ) O(\sqrt{n}) O(n ) 个。那么时间复杂度为 O ( ∑ i = 1 2 1 2 i ) ≈ O ( n 3 4 ) O(\sum\limits_{i=1}2^{\frac{1}{2^i}}) \approx O(n^{\frac{3}{4}}) O(i=1∑22i1)≈O(n43)
这明显会超时,我们需要进一步优化。其实可以这样想,一部分前缀和我们先预处理出来,另一部分再通过杜教筛来算。我们设预处理前 k k k 个,后 n − k n-k n−k 个用杜教筛做,那么此时时间复杂度是 O ( k + ( n − k ) 3 4 ) O(k+(n-k)^{\frac{3}{4}}) O(k+(n−k)43),由基本不等式得, k = ( n − k ) 3 4 k=(n-k)^{\frac{3}{4}} k=(n−k)43 原式取最小值。我们算一下发现 k = n 2 3 k=n^{\frac{2}{3}} k=n32 是不错的选择,此时复杂度大约为 O ( n 2 3 ) O(n^{\frac{2}{3}}) O(n32)。
//Don't act like a loser. //You can only use the code for studying or finding mistakes //Or,you'll be punished by Sakyamuni!!! #include<bits/stdc++.h> #define int long long using namespace std; int read() { char ch=getchar(); int f=1,x=0; while(ch<'0'||ch>'9') { if(ch=='-') f=-1; ch=getchar(); } while(ch>='0'&&ch<='9') { x=x*10+ch-'0'; ch=getchar(); } return f*x; } const int maxn=4e7+10; int n,mu[maxn],p[maxn/10],cnt,s[maxn]; bool is[maxn]; map<long,long> mp;//记忆化搜索 void sieve_mu(int n) { fill(is,is+n+1,1); mu[1]=1; for(int i=2;i<=n;i++) { if(is[i]) { p[++cnt]=i; mu[i]=-1; } for(int j=1;j<=cnt;j++) { if(p[j]*i>n) { break; } is[p[j]*i]=0; if(i%p[j]==0) { mu[i*p[j]]=0; break; } mu[i*p[j]]=-mu[i]; } } for(int i=1;i<=n;i++){ s[i]=s[i-1]+mu[i]; } } int pre(int n) { if(n<=4e7) { return s[n]; } if(mp[n]) { return mp[n]; } int j=2,ret=1; for(int i=2;i<=n;i=j+1) { j=(n/(n/i));//数论分块 ret-=(j-i+1)*pre(n/i); } return mp[n]=ret; } signed main() { sieve_mu(4e7); int t=read(); while(t--) { printf("%lld\n",pre(read())); } return 0; }令 S ( n ) = ∑ i = 1 n φ ( i ) ( n < 2 31 ) S(n)=\sum\limits_{i=1}^n \varphi(i)\ \ \ \ (n<2^{31}) S(n)=i=1∑nφ(i) (n<231)
我们根据公式来求,我们令 f = φ , g = 1 f=\varphi,g=1 f=φ,g=1,由 Dirichlet 卷积知识,我们知道 φ ∗ 1 = i d \varphi\ast 1=id φ∗1=id。那么:
g ( 1 ) ⋅ S ( n ) = ∑ i = 1 n ( f ∗ g ) ( i ) − ∑ i = 2 n g ( i ) ⋅ S ( ⌊ n i ⌋ ) 1 ⋅ S ( n ) = ∑ i = 1 n i d ( i ) − ∑ i = 2 n 1 ⋅ S ( ⌊ n i ⌋ ) S ( n ) = n 2 + n 2 − ∑ i = 2 n S ( ⌊ n i ⌋ ) \begin{aligned} g(1)\cdot S(n)=\sum\limits_{i=1}^n (f\ast g)(i)-\sum\limits_{i=2}^n g(i)\cdot S(\lfloor\frac{n}{i}\rfloor)\\1\cdot S(n)=\sum\limits_{i=1}^n id(i)-\sum\limits_{i=2}^n 1\cdot S(\lfloor\frac{n}{i}\rfloor)\\S(n)=\frac{n^2+n}{2}-\sum\limits_{i=2}^n S(\lfloor\frac{n}{i}\rfloor) \end{aligned} g(1)⋅S(n)=i=1∑n(f∗g)(i)−i=2∑ng(i)⋅S(⌊in⌋)1⋅S(n)=i=1∑nid(i)−i=2∑n1⋅S(⌊in⌋)S(n)=2n2+n−i=2∑nS(⌊in⌋)
和之前一样,我们还是采取一部分直接算,一部分杜教筛的想法,时间复杂度为 O ( n 2 3 ) O(n^{\frac{2}{3}}) O(n32)
//Don't act like a loser. //You can only use the code for studying or finding mistakes //Or,you'll be punished by Sakyamuni!!!> #define int long long using namespace std; int read() { char ch=getchar(); int f=1,x=0; while(ch<'0'||ch>'9') { if(ch=='-') f=-1; ch=getchar(); } while(ch>='0'&&ch<='9') { x=x*10+ch-'0'; ch=getchar(); } return f*x; } const int maxn=1e7+10; int n,phi[maxn],p[maxn/10],cnt,s[maxn]; bool is[maxn]; map<long,long> mp; void sieve_euler(int n) { fill(is,is+n+1,1); phi[1]=1; for(int i=2;i<=n;i++) { if(is[i]) { p[++cnt]=i; phi[i]=i-1; } for(int j=1;j<=cnt;j++) { if(p[j]*i>n) { break; } is[p[j]*i]=0; if(i%p[j]==0) { phi[i*p[j]]=p[j]*phi[i]; break; } phi[i*p[j]]=(p[j]-1)*phi[i]; } } for(int i=1;i<=n;i++){ s[i]=s[i-1]+phi[i]; } } int pre(int n) { if(n<=1e7) { return s[n]; } if(mp[n]) { return mp[n]; } int j=2,ret=(n*(n+1)/2); for(int i=2;i<=n;i=j+1) { j=(n/(n/i)); ret-=(j-i+1)*pre(n/i); } return mp[n]=ret; } signed main() { sieve_euler(1e7); int t=read(); while(t--) { printf("%lld\n",pre(read())); } return 0; }这两个代码结合起来就可以通过洛谷上的模板题。
令 S ( n ) = ∑ i = 1 n μ 2 ( i ) ( n < 2 31 ) S(n)=\sum\limits_{i=1}^n \mu^2(i)\ \ \ \ (n<2^{31}) S(n)=i=1∑nμ2(i) (n<231)
我们继续根据公式来求,我们令 f = μ 2 , g = 1 f=\mu^2,g=1 f=μ2,g=1,由 Dirichlet 卷积知识,我们知道 μ 2 ∗ 1 = μ \mu^2*1=\mu μ2∗1=μ。那么:
g ( 1 ) ⋅ S ( n ) = ∑ i = 1 n ( f ∗ g ) ( i ) − ∑ i = 2 n g ( i ) ⋅ S ( ⌊ n i ⌋ ) 1 ⋅ S ( n ) = ∑ i = 1 n μ ( i ) − ∑ i = 2 n 1 ⋅ S ( ⌊ n i ⌋ ) S ( n ) = ∑ i = 1 n μ ( i ) − ∑ i = 2 n S ( ⌊ n i ⌋ ) \begin{aligned} g(1)\cdot S(n)=\sum\limits_{i=1}^n (f\ast g)(i)-\sum\limits_{i=2}^n g(i)\cdot S(\lfloor\frac{n}{i}\rfloor)\\1\cdot S(n)=\sum\limits_{i=1}^n \mu(i)-\sum\limits_{i=2}^n 1\cdot S(\lfloor\frac{n}{i}\rfloor)\\S(n)=\sum\limits_{i=1}^n \mu(i)-\sum\limits_{i=2}^n S(\lfloor\frac{n}{i}\rfloor) \end{aligned} g(1)⋅S(n)=i=1∑n(f∗g)(i)−i=2∑ng(i)⋅S(⌊in⌋)1⋅S(n)=i=1∑nμ(i)−i=2∑n1⋅S(⌊in⌋)S(n)=i=1∑nμ(i)−i=2∑nS(⌊in⌋)
所以我们在求莫比乌斯函数平方和时,要求出莫比乌斯函数的前缀和,也是杜教筛,总时间复杂度 O ( n 2 3 ) O(n^{\frac{2}{3}}) O(n32)。
令 S ( n ) = ∑ i = 1 n ( μ ( i ) ) 2 ( n < 2 31 ) S(n)=\sum\limits_{i=1}^n (\mu(i))^2\ \ \ \ (n<2^{31}) S(n)=i=1∑n(μ(i))2 (n<231)
这个题和之前的就有所不同了。我们令 f ( i ) = μ ( i ) 2 f(i)=\mu(i)^2 f(i)=μ(i)2,但是要找一个方便计算的 g g g 才行。
我们选取函数 g ( x ) = [ x = k 2 k ∈ N + ] g(x)=[x=k^2\ \ \ k\in N^+] g(x)=[x=k2 k∈N+]。我们来算一下 g g g 和 f f f 的狄利克雷卷积。我们会发现 f ∗ g = 1 f\ast g=1 f∗g=1?!简要证明一下:
( f ∗ g ) ( n ) = ∑ d ∣ n g ( d ) ⋅ f ( n d ) (f\ast g)(n)=\sum\limits_{d\mid n} g(d)\cdot f(\frac{n}{d}) (f∗g)(n)=d∣n∑g(d)⋅f(dn)。首先分类讨论,如果 d d d 是一个完全平方数,只有当 d d d 是 n n n 的所有约数中最大的完全平方数时,乘积为 1 1 1,否则 μ ( n d ) = 0 \mu(\frac{n}{d})=0 μ(dn)=0。如果 d d d 不是完全平方数,那么 g ( d ) = 0 g(d)=0 g(d)=0。得证。
那我们的计算就简化了不少。
g ( 1 ) ⋅ S ( n ) = ∑ i = 1 n ( f ∗ g ) ( i ) − ∑ i = 2 n g ( i ) ⋅ S ( ⌊ n i ⌋ ) 1 ⋅ S ( n ) = ∑ i = 1 n 1 − ∑ i = 2 n 1 ⋅ S ( ⌊ n i 2 ⌋ ) S ( n ) = n − ∑ i = 2 n S ( ⌊ n i 2 ⌋ ) \begin{aligned} g(1)\cdot S(n)=\sum\limits_{i=1}^n (f\ast g)(i)-\sum\limits_{i=2}^n g(i)\cdot S(\lfloor\frac{n}{i}\rfloor)\\1\cdot S(n)=\sum\limits_{i=1}^n 1-\sum\limits_{i=2}^{\sqrt{n}} 1\cdot S(\lfloor\frac{n}{i^2}\rfloor)\\S(n)=n-\sum\limits_{i=2}^{\sqrt{n}} S(\lfloor\frac{n}{i^2}\rfloor) \end{aligned} g(1)⋅S(n)=i=1∑n(f∗g)(i)−i=2∑ng(i)⋅S(⌊in⌋)1⋅S(n)=i=1∑n1−i=2∑n 1⋅S(⌊i2n⌋)S(n)=n−i=2∑n S(⌊i2n⌋)
我们甚至不用数论分块就可以解决此题。采用同样的优化方法,时间复杂度还是约为 O ( n 2 3 ) O(n^{\frac{2}{3}}) O(n32)
相关练习题:
模板(简单版)模板(强化版)完全平方数SumLucas的数论 自己找