杜教筛可以解决哪一类的问题:
例如题目给你一个函数,让你求它的前项和 ∑ i = 1 n f ( i ) \sum_{i=1}^{n}f(i) ∑i=1nf(i) ,但是这个n很大(n=1e10),显然在秒数级是不能算出来的,之前我们有学过拉格朗日插值定理求前n项和,它的复杂度与这个函数最高阶k紧密相连,差不多为O(k),但是你要想用拉格朗日插值法,你得知道这个函数f为多项式函数才行。比如让你求前n项欧拉函数和,莫比乌斯前n项和,你就用不上这个拉格朗日插值法了。
此时,为了解决这个问题,杜教筛就出来了,它就是用来求这些的。我们先大概说下他的原理:我们先在毫秒复杂度的基础上预处理前T项和的结果(T<n),然后前n项和我们通过我们构造的函数表达式,用整数分块来去求解,此时复杂度会降为O(
n
2
3
n^{\frac{2}{3}}
n32)左右。
杜教筛也就是积性函数前n项和的参考博客:
神犇1
神犇2
下面写的如果有跟网上雷同,请不要介意,我只是写来给我以后回顾总结用的。
先认识下几个函数先:
函数f和g的卷积为:
(
f
∗
g
)
(
n
)
=
∑
d
∣
n
f
(
d
)
∗
g
(
n
d
)
(f*g)(n)=\sum_{d|n}f(d)*g(\frac{n}{d})
(f∗g)(n)=∑d∣nf(d)∗g(dn)
元函数
ϵ
(
n
)
\epsilon(n)
ϵ(n),满足
ϵ
(
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
常用公式:
1,
[
n
=
=
1
]
=
∑
d
∣
n
u
(
d
)
[n==1]=\sum_{d|n}u(d)
[n==1]=∑d∣nu(d)
2,
∑
i
=
1
n
[
g
c
d
(
n
,
i
)
=
1
]
∗
i
=
n
∗
φ
(
n
)
+
[
n
=
=
1
]
2
\sum_{i=1}^{n}[gcd(n,i)=1]*i=\frac{n*\varphi(n)+[n==1]}{2}
∑i=1n[gcd(n,i)=1]∗i=2n∗φ(n)+[n==1]
3,
φ
(
n
)
=
∑
d
∣
n
u
(
d
)
n
d
\varphi(n)=\sum_{d|n}u(d)\frac{n}{d}
φ(n)=∑d∣nu(d)dn
4,
φ
(
n
)
n
=
∑
d
∣
n
u
(
d
)
d
\frac{\varphi(n)}{n}=\sum_{d|n}\frac{u(d)}{d}
nφ(n)=∑d∣ndu(d)
5,
n
=
∑
d
∣
n
φ
(
d
)
n=\sum_{d|n}\varphi(d)
n=∑d∣nφ(d)
杜教筛的通用表达式:
已知我们要求函数f的前n项和,我们设
S
(
n
)
=
∑
i
=
1
n
f
(
i
)
S(n)=\sum_{i=1}^{n}f(i)
S(n)=∑i=1nf(i),我们要找到函数g,并且函数f与函数g的卷积为
h
=
f
∗
g
h=f*g
h=f∗g,那么我们有如下表达式(证明见参考博客链接):
g
(
1
)
S
(
n
)
=
∑
i
=
1
n
h
(
i
)
−
∑
d
=
2
n
g
(
d
)
∗
S
(
⌊
n
d
⌋
)
g(1)S(n)=\sum_{i=1}^{n}h(i)-\sum_{d=2}^{n}g(d)*S(\left \lfloor \frac{n}{d} \right \rfloor)
g(1)S(n)=∑i=1nh(i)−∑d=2ng(d)∗S(⌊dn⌋)
通常我们会拿恒等函数
I
I
I给g去和f卷积。
例如1:求莫比乌斯前n项和:
∑
i
=
1
n
u
(
i
)
\sum_{i=1}^{n}u(i)
∑i=1nu(i)
h
=
u
∗
I
=
∑
d
∣
n
u
(
d
)
=
ϵ
h=u*I=\sum_{d|n}u(d)=\epsilon
h=u∗I=∑d∣nu(d)=ϵ(莫比乌斯函数性质)
故最终式子就为:
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=2nS(⌊dn⌋)
例如2:求欧拉函数前n项和:
∑
i
=
1
n
φ
(
i
)
\sum_{i=1}^{n}\varphi(i)
∑i=1nφ(i)
我们函数拿恒等函数给g去和f卷积,
h
=
φ
∗
I
=
∑
d
∣
n
φ
(
d
)
=
i
d
(
n
)
=
n
h=\varphi*I=\sum_{d|n}\varphi(d)=id(n)=n
h=φ∗I=∑d∣nφ(d)=id(n)=n (欧拉函数性质)
故最终的式子就为:
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=1ni−∑d=2nS(⌊dn⌋)
例如2:题目链接:哆啦A梦传送门
题意:已知
n
2
−
3
∗
n
+
2
=
∑
d
∣
n
f
(
d
)
n^2-3*n+2=\sum_{d|n}f(d)
n2−3∗n+2=∑d∣nf(d)
求
∑
i
=
1
n
f
(
i
)
m
o
d
(
1
e
9
+
7
)
\sum_{i=1}^{n}f(i) \quad mod(1e9+7)
∑i=1nf(i)mod(1e9+7) (
n
≤
1
e
9
n\leq 1e9
n≤1e9)
这题是很模板的一道题,很显然我们也拿恒等函数
I
(
n
)
I(n)
I(n)去和f卷积,得:
h
=
f
∗
I
=
∑
d
∣
n
f
(
d
)
=
n
2
−
3
∗
n
+
2
h=f*I=\sum_{d|n}f(d)=n^2-3*n+2
h=f∗I=∑d∣nf(d)=n2−3∗n+2
故最终的式子就为:
S
(
n
)
=
∑
i
=
1
n
(
i
2
−
3
∗
n
+
2
)
−
∑
d
=
2
n
S
(
⌊
n
d
⌋
)
=
n
∗
(
n
−
1
)
∗
(
n
−
2
)
3
−
∑
d
=
2
n
S
(
⌊
n
d
⌋
)
S(n)=\sum_{i=1}^{n}(i^2-3*n+2)-\sum_{d=2}^{n}S(\left \lfloor \frac{n}{d} \right \rfloor) =\frac{n*(n-1)*(n-2)}{3}-\sum_{d=2}^{n}S(\left \lfloor \frac{n}{d} \right \rfloor)
S(n)=∑i=1n(i2−3∗n+2)−∑d=2nS(⌊dn⌋)=3n∗(n−1)∗(n−2)−∑d=2nS(⌊dn⌋)
我们设
g
(
n
)
=
∑
d
∣
n
f
(
i
)
=
i
2
−
3
∗
i
+
2
g(n)=\sum_{d|n}f(i)=i^2-3*i+2
g(n)=∑d∣nf(i)=i2−3∗i+2,由莫比乌斯反演可得:
f
(
n
)
=
∑
d
∣
n
u
(
d
)
g
(
n
d
)
f(n)=\sum_{d|n}u(d)g(\frac{n}{d})
f(n)=∑d∣nu(d)g(dn)
我们先预处理函数f前600000项来。
代码:
#include<bits/stdc++.h>
#include<tr1/unordered_map>
using namespace std;
typedef long long LL;
const LL mod=1e9+7;
LL n;
#define N 600000
LL sum[N+10],f[N+10];
bool vis[N+10];
int mu[N+10],tot,prime[N+10];
tr1::unordered_map<LL,LL> w;
void init()
{
tot=0;
mu[1]=1;
for(int i=2;i<=N;i++)
{
if(!vis[i]){
prime[++tot]=i;
mu[i]=-1;
}
for(int j=1;j<=tot&&prime[j]*i<=N;j++)
{
vis[i*prime[j]]=1;
if(i%prime[j]==0) break;
else mu[i*prime[j]]=-mu[i];
}
}
LL item,t;
for(int d=1;d<=N;d++)///枚举n的因子d
{
for(int n=d;n<=N;n+=d){ ///计算f(n)
t=(LL)(n/d);
item=(t*t%mod-3*t%mod+2+mod)%mod;///计算g(n/d)的值
f[n]=(f[n]+item*mu[d]%mod+mod)%mod;
}
}
///预处理
sum[0]=0;
for(int i=1;i<=N;i++)
sum[i]=(sum[i-1]+f[i])%mod;
}
int num=0;
LL djs(LL x)
{
///在预处理的范围内,直接返回
if(x<=600000) return sum[x];
if(w[x]) return w[x]; ///以及算好,直接返回
LL t=x%mod;
LL ans=t*(t-1)%mod*(t-2)%mod*333333336;
for(LL l=2,r;l<=x;l=r+1)///整数分块
{
r=x/(x/l);
ans=(ans-(r-l+1)*djs(x/l)%mod+mod)%mod;
num++;
}
return w[x]=ans;
}
int main()
{
init();
int ncase;
scanf("%d",&ncase);
while(ncase--)
{
scanf("%lld",&n);
printf("%lld\n",djs(n));
}
return 0;
}
我的标签:做个有情怀的程序员。