漫谈杜教筛

UPD 2019/01/05 最近学到了杜教筛,正好把以前的代码优化了一遍^_^

首先约定两个记号: [ x ] [x] 表示如果命题 x x 为真则值为 1 1 ,否则为 0 0 x y x\perp y 表示 x x y y 互质。

先从一个问题开始:求
f ( n ) = i = 1 n μ ( i ) f(n)=\sum_{i=1}^n\mu(i)
其中 μ \mu 代表莫比乌斯函数。

解:由公式
d n μ ( d ) = [ n = 1 ] \sum_{d\mid n}\mu(d)=[n=1]\\


i = 1 n d i μ ( d ) = 1... ( 1 ) \sum_{i=1}^n\sum_{d\mid i}\mu(d)=1...(1)\\
变换一下枚举顺序,得
i = 1 n j = 1 n i μ ( j ) = 1... ( 2 ) i = 1 n μ ( i ) = 1 i = 2 n j = 1 n i μ ( j ) f ( n ) = 1 i = 2 n f ( n i ) \sum_{i=1}^n\sum_{j=1}^{\lfloor\frac ni\rfloor}\mu(j)=1...(2)\\ \sum_{i=1}^n\mu(i)=1-\sum_{i=2}^n\sum_{j=1}^{\lfloor\frac ni\rfloor}\mu(j)\\ f(n)=1-\sum_{i=2}^nf(\lfloor\frac ni\rfloor)

备注:(2)中的 i i j j 分别代表(1)中的 i d \frac id d d

称集合 { n i , 0 < i n } \lbrace\lfloor\frac ni\rfloor,0<i\le n\rbrace 为对 n n 的关键点,则有一个重要定理:对 n i \lfloor\frac ni\rfloor 的关键点包含于对 n n 的关键点

所以分块+递推即可,最多只会关系到 O ( n ) O(\sqrt n) 个值,由积分知识得复杂度 O ( n 3 4 ) O(n^\frac 34)

那么可不可以继续优化呢?答案是可以的。

首先用线性筛预处理较小的值,而对较大的值进行递推,可以优化效率。

设预处理 k k 个值,则由积分知识的复杂度 O ( k + n k ) O(k+\frac n{\sqrt k}) ,容易发现当 k = n 2 3 k=n^\frac 23 时最优,复杂度 O ( n 2 3 ) O(n^\frac 23)

具体实现时还有一个问题:怎么存储对 n n 的关键点?

用哈希表或map当然可以,在这里给大家介绍一个简单方法,可以做到空间复杂度 O ( n ) O(\sqrt n)

  • i n i\le\sqrt n 时,可以暴力开数组存下来;
  • i > n i>\sqrt n 时,注意到 n i \lfloor\frac ni\rfloor 两两不同,所以可以以 n i \lfloor\frac ni\rfloor 为索引用另一个数组存下来。

类似的,我们可以推出杜教筛的一般形式:设
h = f g h=f*g
F , G , H F,G,H 分别是他们的前缀和,则
h ( n ) = i j = n f ( i ) g ( j ) H ( n ) = i j n f ( i ) g ( i ) = i = 1 n F ( n i ) g ( i ) F ( n ) g ( 1 ) = H ( n ) i = 2 n F ( n i ) g ( i ) h(n)=\sum_{ij=n}f(i)g(j)\\ H(n)=\sum_{ij\le n}f(i)g(i)=\sum_{i=1}^nF(\lfloor\frac ni\rfloor)g(i)\\ F(n)g(1)=H(n)-\sum_{i=2}^nF(\lfloor\frac ni\rfloor)g(i)

如果 G , H G,H 都可以快速求出,那么 F F 也可以在 O ( n 2 3 ) O(n^\frac 23) 内求出

如果令
f = μ , g = 1 , h = e f=\mu,g=1,h=e
那么这就是上题的解法。

例题:51nod 1244 莫比乌斯函数之和

传送门


i = a b μ ( i ) ( a b 1 0 10 ) \sum_{i=a}^b\mu(i)(a\le b\le10^{10})
解:答案就是
i = 1 b μ ( i ) i = 1 a 1 μ ( i ) \sum_{i=1}^b\mu(i)-\sum_{i=1}^{a-1}\mu(i)
话不多说,直接上代码。

#include<bits/stdc++.h>
using namespace std;
#define ll long long
const ll N=8000005;
ll n,n2,mu[N],tot,a,b,ar[N],vis[N];
int p[N],v[N];
void init(){
	mu[1]=1;
	for(ll i=2;i<N;i++){
		if(!v[i])p[++p[0]]=i,mu[i]=-1;
		for(ll j=1;j<=p[0]&&i*p[j]<N;j++){
			v[i*p[j]]=1;
			if(!(i%p[j]))break;
			mu[i*p[j]]=-mu[i];
		}
	}
	for(ll i=2;i<N;i++)mu[i]+=mu[i-1];
}
ll _solve(ll a){
	if(a<N)return mu[a];
	if(vis[n/a])return ar[n/a];vis[n/a]=1;
	ll ans=1;
	for(ll i=2,j;i<=a;i=j+1)j=a/(a/i),ans-=(j-i+1)*_solve(a/i);
	return ar[n/a]=ans;
}
ll solve(ll a){n=a,n2=sqrt(n+0.5),memset(vis,0,sizeof(vis));return _solve(n);}
int main(){
	init(),scanf("%lld%lld",&a,&b),printf("%lld",solve(b)-solve(a-1));
	return 0;
}

BZOJ3944 Sum

传送门

直接上结论:

第一问取
f = ϕ , g = 1 , h = i d f=\phi,g=1,h=id

F ( n ) g ( 1 ) = H ( n ) i = 2 F ( n i ) g ( i ) F ( n ) = n ( n + 1 ) 2 i = 2 F ( n i ) F(n)g(1)=H(n)-\sum_{i=2}F(\lfloor\frac ni\rfloor)g(i)\\ F(n)=\frac {n(n+1)}2-\sum_{i=2}F(\lfloor\frac ni\rfloor)\\
第二问取
f = μ , g = 1 , h = e f=\mu,g=1,h=e

F ( n ) g ( 1 ) = H ( n ) i = 2 F ( n i ) g ( i ) F ( n ) = 1 i = 2 F ( n i ) F(n)g(1)=H(n)-\sum_{i=2}F(\lfloor\frac ni\rfloor)g(i)\\ F(n)=1-\sum_{i=2}F(\lfloor\frac ni\rfloor)\\

#pragma GCC optimize(2)
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const ll N=2500000;
ll t,n,phi0[N],mu0[N],q[N],vis0[N],mu[N],phi[N],vis[N];
void init(){
    scanf("%lld",&t);
    mu0[1]=phi0[1]=1;
    for(ll i=2;i<N;i++){
        if(!vis0[i])q[++q[0]]=i,mu0[i]=-1,phi0[i]=i-1;
        for(ll j=1;j<=q[0]&&i*q[j]<N;j++){
            ll k=i*q[j];
            vis0[k]=1;
            if(!(i%q[j])){phi0[k]=phi0[i]*q[j];break;}
            mu0[k]=-mu0[i],phi0[k]=phi0[i]*(q[j]-1);
        }
    }
    for(ll i=2;i<N;i++)mu0[i]+=mu0[i-1],phi0[i]+=phi0[i-1];
}
pair<ll,ll> get(ll a){
    if(a<N)return make_pair(mu0[a],phi0[a]);
    if(vis[n/a])return make_pair(mu[n/a],phi[n/a]);vis[n/a]=1;
    ll ans=1,ans2;
    if(a&1)ans2=a*((a+1)>>1);
    else   ans2=(a>>1)*(a+1);
    for(ll i=2,j;i<=a;i=j+1){
        j=a/(a/i);pair<ll,ll> p=get(a/i);
        ans -=(j-i+1)*p.first;
        ans2-=(j-i+1)*p.second;
    }
    mu[n/a]=ans,phi[n/a]=ans2;
    return make_pair(ans,ans2);
}
void solve(){
    while(t--){
        scanf("%lld",&n);
        memset(vis,0,sizeof(vis));
        pair<ll,ll>s=get(n);
        printf("%lld %lld\n",s.second,s.first);
    }
}
int main(){
    init(),solve();
    return 0;
}

例题:51nod 1237 最大公约数之和 V3

传送门

(原题题面有误)求
i = 1 n j = 1 n g c d ( i , j ) ( n 1 0 10 ) \sum_{i=1}^n\sum_{j=1}^ngcd(i,j)(n\le10^{10})
解:由莫比乌斯反演,得原式=
d = 1 n ϕ ( d ) n d \sum_{d=1}^n\phi(d)\lfloor\frac nd\rfloor
后面一部分可以通过分块搞定,问题在于怎么快速求出欧拉函数的前缀和

容易想到用杜教筛,在上面的方法中令
f = ϕ , g = 1 , h = i d f=\phi,g=1,h=id
那么即可轻松搞定。

记得取模!记得取模!记得取模!重要的事情说三遍!(注意 n n 本身已经超出了 1 0 9 + 7 10^9+7 的范围)

#include<bits/stdc++.h>
using namespace std;
#define ll long long
const ll N=15000000,p=1000000007,p2=500000004;
ll n,n2,phi0[N],ans,phi[N],vis[N];
int pr[N],v[N];
void init(){
	scanf("%lld",&n),n2=sqrt(n+0.5);
	phi0[1]=1;
	for(ll i=2;i<N;i++){
		if(!v[i])pr[++pr[0]]=i,phi0[i]=i-1;
		for(ll j=1;j<=pr[0]&&i*pr[j]<N;j++){
			v[i*pr[j]]=1;
			if(!(i%pr[j])){phi0[i*pr[j]]=phi0[i]*pr[j];break;}
			phi0[i*pr[j]]=phi0[i]*(pr[j]-1);
		}
	}
	for(ll i=2;i<N;i++)(phi0[i]+=phi0[i-1])%=p;
}
ll get(ll a){
	if(a<N)return phi0[a];
	if(vis[n/a])return phi[n/a];vis[n/a]=1;
	ll ans=((a%p)*p2%p)*((a+1)%p)%p;
	for(ll i=2,j;i<=a;i=j+1){
		j=a/(a/i);
		ll cont=((j-i+1)%p)*((p-get(a/i))%p)%p;
		(ans+=cont)%=p;
	}
	return phi[n/a]=ans;
}
void solve(){
	for(ll i=1,j;i<=n;i=j+1){
		j=n/(n/i);
		ll cont=(((n/i)%p)*((n/i)%p)%p)*((get(j)-get(i-1)+p)%p)%p;
		(ans+=cont)%=p;
	}
	printf("%lld",ans);
}
int main(){
	init(),solve();
	return 0;
}

例题:51nod 1238 最小公倍数之和 V3

传送门

(原题题面有误)求
i = 1 n j = 1 n l c m ( i , j ) ( n 1 0 10 ) \sum_{i=1}^n\sum_{j=1}^nlcm(i,j)(n\le10^{10})
解:原式=
d = 1 n 1 d i = 1 n d i j = 1 n d j [ i j ] \sum_{d=1}^n\frac1d\sum_{i=1}^{\lfloor\frac nd\rfloor}i\sum_{j=1}^{\lfloor\frac nd\rfloor}j[i\perp j]
只看后面,得
i = 1 n d i j = 1 n d j [ i j ] = 2 × i = 1 n d i j = 1 i j [ i j ] 1 = 2 × i = 1 n d i × i ϕ ( i ) + [ i = 1 ] 2 1 = i = 1 n d i 2 ϕ ( i ) \sum_{i=1}^{\lfloor\frac nd\rfloor}i\sum_{j=1}^{\lfloor\frac nd\rfloor}j[i\perp j]\\ =2\times\sum_{i=1}^{\lfloor\frac nd\rfloor}i\sum_{j=1}^ij[i\perp j]-1\\ =2\times\sum_{i=1}^{\lfloor\frac nd\rfloor}i\times\frac{i\phi(i)+[i=1]}2-1\\ =\sum_{i=1}^{\lfloor\frac nd\rfloor}i^2\phi(i)\\
所以原式=
d = 1 n d i = 1 n d i 2 ϕ ( i ) \sum_{d=1}^nd\sum_{i=1}^{\lfloor\frac nd\rfloor}i^2\phi(i)
我们需要快速求出
i = 1 n d i 2 ϕ ( i ) \sum_{i=1}^{\lfloor\frac nd\rfloor}i^2\phi(i)
在上面的方法中令
f = i 2 ϕ ( i ) , g = i 2 , h = i 3 f=i^2\phi(i),g=i^2,h=i^3
分块+杜教筛即可轻松搞定。

#include<bits/stdc++.h>
using namespace std;
#define ll long long
const ll N=15000000,p=1000000007,p2=500000004,p4=250000002,p6=166666668;
ll n,n2,phi0[N],ans,phi[N],vis[N];
int pr[N],v[N];
void init(){
	scanf("%lld",&n),n2=sqrt(n+0.5);
	phi0[1]=1;
	for(ll i=2;i<N;i++){
		if(!v[i])pr[++pr[0]]=i,phi0[i]=i-1;
		for(ll j=1;j<=pr[0]&&i*pr[j]<N;j++){
			v[i*pr[j]]=1;
			if(!(i%pr[j])){phi0[i*pr[j]]=phi0[i]*pr[j];break;}
			phi0[i*pr[j]]=phi0[i]*(pr[j]-1);
		}
	}
	for(ll i=2;i<N;i++)(phi0[i]*=i*i%p)%=p;
	for(ll i=2;i<N;i++)(phi0[i]+=phi0[i-1])%=p;
}
ll sum1(ll a){return ((a%p)*((a+1)%p)%p)*p2%p;}
ll sum2(ll a){return ((a%p)*((a+1)%p)%p)*((((a<<1)+1)%p)*p6%p)%p;}
ll sum3(ll a){ll x=sum1(a);return x*x%p;}
ll get(ll a){
	if(a<N)return phi0[a];
	if(vis[n/a])return phi[n/a];vis[n/a]=1;
	ll ans=sum3(a);
	for(ll i=2,j;i<=a;i=j+1){
		j=a/(a/i);
		ll cont=((sum2(j)-sum2(i-1))%p)*((p-get(a/i))%p)%p;
		(ans+=cont)%=p;
	}
	return phi[n/a]=ans;
}
void solve(){
	for(ll i=1,j;i<=n;i=j+1){
		j=n/(n/i);
		ll cont=((p+sum1(j)-sum1(i-1))%p)*(get(n/i)%p)%p;
		(ans+=cont)%=p;
	}
	printf("%lld",ans);
}
int main(){
	init(),solve();
	return 0;
}

猜你喜欢

转载自blog.csdn.net/qq_27327327/article/details/80724681