2019.01.21【BZOJ3944】【洛谷P4213】Sum(杜教筛)

洛谷传送门

BZOJ传送门


解析:

为什么这道题不在zxyoi的模板分类那一栏呢?

因为zxyoi要更这道题的题解,但是他从不更新模板题解

因为数学题代码是没有模板的,只有其中的思路是相通的。

所以zxyoi这里就直接开始推了。

前置姿势:

整除分块,积性函数, D i r i c h l e t Dirichlet Dirichlet卷积,莫比乌斯反演

杜教筛:

应该都知道杜教是谁吧,不知道的请自行百度。

所以杜教筛是个什么东西,由杜教发明的一种在低于线性时间内处理出积性函数前缀和的东西。

杜教筛要解决的问题就是求这样一个函数 S ( n ) S(n) S(n)的值: S ( n ) = ∑ i = 1 n f ( i ) S(n)=\sum_{i=1}^{n}f(i) S(n)=i=1nf(i)

其中 f ( i ) f(i) f(i)是积性函数。

考虑一般怎么求解,既然 f f f是积性函数,那么我们就可以线性筛了啊,然后求出前缀和,不就 O ( n ) O(n) O(n)了吗。

好的,当 n ≤ 1 e 11 n\leq 1e11 n1e11呢?

我们这时候需要一个低于线性时间复杂度的方法,这就是杜教筛要做的事情。

解决:

我们现在找到另一个积性函数 g ( i ) g(i) g(i),把 f f f g g g做一下 D i r i c h l e t Dirichlet Dirichlet卷积,求前缀和: ∑ i = 1 n ( f ∗ g ) ( i ) = ∑ i = 1 n ∑ d ∣ i f ( i d ) g ( 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(f*g)(i)\\ =&\sum_{i=1}^n\sum_{d\mid i}f(\frac{i}d)g(d)\\ =&\sum_{d=1}^{n}g(d)\sum_{i=1}^{\lfloor\frac{n}d\rfloor}f(i)\\ =&\sum_{d=1}^ng(d)S(\lfloor\frac{n}{d}\rfloor) \end{aligned} ===i=1n(fg)(i)i=1ndif(di)g(d)d=1ng(d)i=1dnf(i)d=1ng(d)S(dn)

我们考虑求 g ( 1 ) S ( n ) g(1)S(n) g(1)S(n),通过基础差分可以知道: ∑ i = 1 n g ( d ) S ( ⌊ n d ⌋ ) − ∑ i = 2 n g ( d ) S ( ⌊ n d ⌋ ) \sum_{i=1}^{n}g(d)S(\lfloor\frac{n}d\rfloor)-\sum_{i=2}^ng(d)S(\lfloor\frac{n}d\rfloor) i=1ng(d)S(dn)i=2ng(d)S(dn)

所以杜教筛基础就完了,我们得到一个递归式
g ( 1 ) S ( n ) = ∑ i = 1 n ( f ∗ g ) ( i ) − ∑ i = 2 n g ( d ) S ( ⌊ n d ⌋ ) g(1)S(n)=\sum_{i=1}^n(f*g)(i)-\sum_{i=2}^ng(d)S(\lfloor\frac{n}d\rfloor) g(1)S(n)=i=1n(fg)(i)i=2ng(d)S(dn)

这个式子用整除分块+递归求解+记忆化来实现,就已经十分优秀了。

至于具体的复杂度是多少,我们来算一算: T ( n ) = ∑ i = 1 n O ( i ) + O ( n i ) = O ( n 3 4 ) T(n)=\sum_{i=1}^{\sqrt{n}}O(\sqrt i)+O(\sqrt\frac{n}{i})=O(n^{\frac{3}4}) T(n)=i=1n O(i )+O(in )=O(n43)

这个可以把求和转换成积分后得到,详细的就不再叙述,以免偏离重点。

对于小部分的数据我们可以预处理来做到 O ( n 2 3 ) O(n^{\frac{2}3}) O(n32),考虑呢预处理前 m m m个之后: T ( n ) = ∑ i = 1 ⌊ n m ⌋ O ( n i ) + O ( m ) = O ( n m + O ( m ) ) T(n)=\sum_{i=1}^{\lfloor\frac{n}m\rfloor}O(\sqrt\frac{n}i)+O(m)=O(\sqrt{\frac{n}m}+O(m)) T(n)=i=1mnO(in )+O(m)=O(mn +O(m))

解出来得到 m = O ( n 2 3 ) m=O(n^{\frac{2}3}) m=O(n32)的时候最优,此时 T ( n ) = O ( n 2 3 ) T(n)=O(n^{\frac{2}3}) T(n)=O(n32)

当然这是单个询问的做法,多组数据还是尽量能多预处理就多预处理吧


对于本题:

1.我们要求的是 S ( n ) = ∑ i = 1 n ϕ ( n ) S(n)=\sum_{i=1}^{n}\phi(n) S(n)=i=1nϕ(n)

D i r i c h l e t Dirichlet Dirichlet卷积基础知识: ϕ ∗ 1 = I d \phi*1=Id ϕ1=Id

所以设 f = ϕ , g = 1 , f ∗ g = I d f=\phi,g=1,f*g=Id f=ϕ,g=1,fg=Id

直接得到: S ( n ) = n ( n + 1 ) 2 − ∑ i = 2 n S ( ⌊ n i ⌋ ) S(n)=\frac{n(n+1)}2-\sum_{i=2}^nS(\lfloor\frac{n}i\rfloor) S(n)=2n(n+1)i=2nS(in)

直接做就行了

2.我们要求的是: S ( n ) = ∑ i = 1 n ϕ ( n ) S(n)=\sum_{i=1}^n\phi(n) S(n)=i=1nϕ(n)

D i r i c h l e t Dirichlet Dirichlet卷积基础知识: μ ∗ 1 = ϵ \mu*1=\epsilon μ1=ϵ

f = μ , g = 1 , f ∗ g = ϵ f=\mu,g=1,f*g=\epsilon f=μ,g=1,fg=ϵ

得到: S ( n ) = 1 − ∑ i = 2 n S ( ⌊ n i ⌋ ) S(n)=1-\sum_{i=2}^nS(\lfloor\frac{n}i\rfloor) S(n)=1i=2nS(in)


然后讲几个这道题的坑点。

洛谷上面有点卡常。

BZOJ上面有 n = 0 n=0 n=0 n = 2 31 − 1 n=2^{31}-1 n=2311的数据,记得要处理一下。


代码(递归版,好写但常数略大):

#include<bits/stdc++.h>
#include<tr1/unordered_map>
using namespace std;
#define ll long long
#define re register
#define gc getchar
#define pc putchar
#define cs const

tr1::unordered_map<int,int> Summu;
tr1::unordered_map<int,ll> Sumphi;
cs int P=8000007,N=P-7,INF=0x7fffffff;
int prime[P],pcnt;
bool mark[P]; 
int mu[P];
ll phi[P];
inline void linear_sieves(int len=N){
    phi[1]=mu[1]=1;
    for(int re i=2;i<=len;++i){
        if(!mark[i]){
            prime[++pcnt]=i;
            phi[i]=i-1;
            mu[i]=-1;
        }
        for(int re j=1;i*prime[j]<=len;++j){
            mark[i*prime[j]]=true;
            if(i%prime[j]==0){
                phi[i*prime[j]]=phi[i]*prime[j];
                mu[i*prime[j]]=0;
                break;
            }
            phi[i*prime[j]]=phi[i]*(prime[j]-1);
            mu[i*prime[j]]=-mu[i];
        }
        mu[i]+=mu[i-1];
        phi[i]+=phi[i-1];
    }
}

inline int Get_summu(int n){
    if(n<=N)return mu[n];
    if(Summu[n])return Summu[n];
    int ans=1;
    for(int re i=2,j;j<INF&&i<=n;i=j+1){
        j=n/(n/i);
        ans-=(j-i+1)*Get_summu(n/i);
    }
    return Summu[n]=ans;
}

inline ll Get_sumphi(int n){
    if(n<=N)return phi[n];
    if(Sumphi[n])return Sumphi[n];
    ll ans=n*((ll)n+1)/2;
    for(int re i=2,j;j<INF&&i<=n;i=j+1){
        j=n/(n/i);
        ans-=(j-i+1)*Get_sumphi(n/i);
    }
    return Sumphi[n]=ans;
}

int T;
int n;
signed main(){
    linear_sieves();
    for(scanf("%d",&T);T--;){
        scanf("%d",&n);
        cout<<Get_sumphi(n)<<" "<<Get_summu(n)<<"\n";
    }
    return 0;
}

代码(非递归版,对优点就是跑得贼快):

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define re register
#define gc getchar
#define pc putchar
#define cs const

cs int P=1700006,N=P-6;
int prime[P],pcnt,mu[P];
bool mark[P];
ll phi[P];

inline void linear_sieves(int len=N){
	phi[1]=mu[1]=1;
	for(int re i=2;i<=N;++i){
		if(!mark[i])prime[++pcnt]=i,phi[i]=i-1,mu[i]=-1;
		for(int re j=1,k;(k=i*prime[j])<=len;++j){
			mark[k]=true;
			if(i%prime[j]==0){
				phi[k]=phi[i]*prime[j];
				mu[k]=0;
				break;
			}
			phi[k]=phi[i]*(prime[j]-1);
			mu[k]=-mu[i];
		}
		phi[i]+=phi[i-1];
		mu[i]+=mu[i-1]; 
	}
}

int G[P];
inline int Get_summu(int n){
	if(n==0)return 0;
	int v=n/(int)pow(n,2.0/3);
	int res,last,now;
	for(int re i=v;i;--i){
		int m=n/i;
		res=0;
		for(int re j=2;i*j<=v;++j)res+=G[i*j];
		re int j;
		for(j=v/i+1;(ll)j*j<=m;++j)res+=mu[m/j];
		last=j-1;
		j=m/j;
		for(;j;--j){
			now=m/j;
			res+=mu[j]*(now-last);
			last=now;
		}
		G[i]=1-res;
	}
	return G[1];
}

ll F[P];
inline ll Get_sumphi(int n){
	if(n==0)return 0;
	int v=n/(int)pow(n,2.0/3);
	ll res;int last,now;
	for(int re i=v;i;--i){
		int m=n/i;
		res=0;
		for(int re j=2;i*j<=v;++j)res+=F[i*j];
		int j;
		for(j=v/i+1;(ll)j*j<=m;++j)res+=phi[m/j];
		last=j-1;
		j=m/j;
		for(;j;--j){
			now=m/j;
			res+=phi[j]*(now-last);
			last=now;
		}
		F[i]=m*((ll)m+1)/2-res;
	}
	return F[1];
}

int T,n;
signed main(){
	linear_sieves();
	for(scanf("%d",&T);T--;){
		scanf("%d",&n);
		cout<<Get_sumphi(n)<<" "<<Get_summu(n)<<"\n";
	}
	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值