为了解决一般积性函数前缀和问题,16年集训队论文中提出了洲阁筛
但是洲阁筛思维难度过高,难以理解
后来 在博客中提出了一种基于埃氏筛的扩展算法,大大降低了思维难度
此法对积性函数的唯一要求是 是一个低次多项式,且 可以快速计算
它的时间复杂度为:,空间复杂度:
埃氏筛法每一轮找出第 个质数,并把这个质数的倍数筛掉
对所有 以内的质数执行完之后就可以结束
第一次被 筛掉的 的倍数 必然满足
对于每个 ,我们先来计算下面的式子:
对于质数 , 可以被分解成若干个 之和的形式,接下来只需考虑 的情况
我们之所以这样拆分,是为了保证接下来的推理中 是完全积性函数
值得一提的是,对于原本的 ,仅在质数点满足
但在第一部分的推导中,我们假设所有数的 值都满足这个式子
这与埃氏筛的思想是一致的,即筛法开始之前,默认所有数都是质数
设
其中 表示 的最小质因子
说人话, 就是执行完第 轮埃氏筛后剩下的数的函数值之和
其中包括没被筛掉的合数与处理过的质数
那么我们要求的东西就是
我们尝试从 推出 的答案
若 ,那么第 轮埃氏筛没有筛掉任何数
若 ,那么第 轮埃氏筛会筛掉
这些 的 倍,而 减去前 个质数的函数值就是这些 的函数值之和,同时我们利用完全积性函数的性质得到 ,提出 就可以直接计算了
综合一下,我们得到了 的递推式
事实上,由于做完 轮埃氏筛后,区间 中就只剩下质数了,所以
这样可以简化运算,当然预处理也是可以的
初始值就是还没有执行埃氏筛的状态:
现在我们对于每个 求出了 ,设
质数点的答案为 减去小于 的质数的贡献,即
接下来考虑合数的贡献,枚举合数的最小质因子及其出现次数即可:
保证了没有小于等于 的质因子,这样的话质数幂的情况需要单独考虑
最后的答案显然就是
我们预处理所有 的值,从小到大记为
对于 ,显然
对于 ,观察下面的值:
我们发现 ,所以
这样的话从值到编号就可以 转化了
事实上,代码实现上还是有一些技巧的
第一部分预处理时,我们枚举质数 ,然后分层从大到小递推
第二部分直接递归处理即可
第一部分中,取 即可,求出的 就是答案
#include<bits/stdc++.h> #define FILE "read" #define MAXN 316300 using namespace std; typedef long long ll; ll n,N,cnt,pos[MAXN<<1],g[MAXN<<1],prime[MAXN/10]; inline int Id(ll x){return x<=N?x:pos[0]-n/x+1;} int main(){ freopen(FILE".in","r",stdin); freopen(FILE".out","w",stdout); scanf("%lld",&n); N=sqrt(n); for(ll i=1;i<=n;i=pos[pos[0]]+1) pos[++pos[0]]=n/(n/i); for(int i=1;i<=pos[0];++i) g[i]=pos[i]-1; for(int i=2;i<=N;++i)if(g[i]!=g[i-1]){ prime[++cnt]=i; for(int j=pos[0];1LL*i*i<=pos[j];--j) g[j]-=g[Id(pos[j]/i)]-(cnt-1); } printf("%lld\n",g[pos[0]]); return 0; }
我们用 Min25 筛去水杜教筛模板题,跑得挺快的
按上面的过程走就对了
#include<bits/stdc++.h> #define FILE "read" #define MAXN 46345 using namespace std; typedef long long ll; int n,N,cnt,pos[MAXN<<1];ll g[MAXN<<1],h[MAXN<<1],sum[MAXN/10],prime[MAXN/10]; inline int Id(int x){return x<=N?x:pos[0]-n/x+1;} inline int read(){ int x=0,f=1; char ch=getchar(); while(ch>'9'||ch<'0') {if(ch=='-') f=-1; ch=getchar();} while(ch>='0'&&ch<='9') {x=x*10+ch-'0'; ch=getchar();} return x*f; } inline ll Sum_Phi(int n,int j){ if(n<prime[j]) return 0; ll Ans=(h[Id(n)]-sum[j-1])-(g[Id(n)]-(j-1)); for(int k=j;k<=cnt&&1LL*prime[k]*prime[k]<=n;++k){ int fac=prime[k]; for(int e=1;fac*prime[k]<=n;++e){ Ans+=(Sum_Phi(n/fac,k+1)*(fac/prime[k])+fac)*(prime[k]-1); fac*=prime[k]; } }return Ans; } inline ll Sum_Mu(int n,int j){ if(n<prime[j]) return 0; ll Ans=(j-1)-g[Id(n)]; for(int k=j;k<=cnt&&1LL*prime[k]*prime[k]<=n;++k) Ans-=Sum_Mu(n/prime[k],k+1); return Ans; } void solve(){ n=read(); N=sqrt(n); pos[0]=cnt=0; for(int i=1;i<=n;i=pos[pos[0]]+1) pos[++pos[0]]=n/(n/i); for(int i=1;i<=pos[0];++i) g[i]=1LL*pos[i]-1; for(int i=1;i<=pos[0];++i) h[i]=1LL*pos[i]*(pos[i]+1)/2-1; for(int i=2;i<=N;++i)if(g[i]!=g[i-1]){ prime[++cnt]=i; sum[cnt]=sum[cnt-1]+i; for(int j=pos[0];1LL*i*i<=pos[j];--j){ g[j]=g[j]-(g[Id(pos[j]/i)]-(cnt-1)); h[j]=h[j]-(h[Id(pos[j]/i)]-sum[cnt-1])*i; } } printf("%lld %lld\n",Sum_Phi(n,1)+1,Sum_Mu(n,1)+1); } int main(){ freopen(FILE".in","r",stdin); freopen(FILE".out","w",stdout); int T=read(); while(T--) solve(); return 0; }
对于质数的值
的情况单独处理,其余的就是套路了
#include<bits/stdc++.h> #define FILE "read" #define MAXN 100010 #define cadd(a,b) a=add(a,b) #define csub(a,b) a=sub(a,b) #define cmul(a,b) a=mul(a,b) using namespace std; typedef long long ll; const int mod=1e9+7; int N,cnt,g[MAXN<<1],h[MAXN<<1];ll n,pos[MAXN<<1],prime[MAXN/10]; inline int add(int a,int b){return (a+=b)>=mod?a-mod:a;} inline int sub(int a,int b){return (a-=b)<0?a+mod:a;} inline int mul(int a,int b){return 1LL*a*b%mod;} inline int Pow(int a,int b){ int ret=1; while(b){ if(b&1) cmul(ret,a); cmul(a,a); b>>=1; }return ret; } inline int Inv(int x){return Pow(x,mod-2);} inline int Id(ll x){return x<=N?x:pos[0]-n/x+1;} inline int S(ll n,int j){ if(n<prime[j]) return 0; int Ans=sub(h[Id(n)],h[prime[j-1]]); for(int k=j;k<=cnt&&prime[k]*prime[k]<=n;++k){ ll fac=prime[k]; for(int e=1;fac*prime[k]<=n;++e){ cadd(Ans,add(mul(S(n/fac,k+1),prime[k]^e),prime[k]^(e+1))); fac*=prime[k]; } }return Ans; } int main(){ freopen(FILE".in","r",stdin); freopen(FILE".out","w",stdout); scanf("%lld",&n); N=sqrt(n); for(ll i=1;i<=n;i=pos[pos[0]]+1) pos[++pos[0]]=n/(n/i); for(int i=1;i<=pos[0];++i) g[i]=sub(pos[i]%mod,1); for(int i=1;i<=pos[0];++i) h[i]=mul(mul((pos[i]+2)%mod,(pos[i]-1)%mod),Inv(2)); for(int i=2;i<=N;++i)if(g[i]!=g[i-1]){ prime[++cnt]=i; for(int j=pos[0];1LL*i*i<=pos[j];--j){ csub(g[j],sub(g[Id(pos[j]/i)],cnt-1)); csub(h[j],mul(sub(h[Id(pos[j]/i)],h[prime[cnt-1]]),i)); } } for(int i=1;i<=pos[0];++i) h[i]+=(i>1)*2-g[i]; printf("%d\n",add(S(n,1),1)); return 0; }
注意到:
然后就无脑 Min25 筛了
至于对 取模就相当于开个 unsigned long long 让它自然溢出即可
顺便可以水掉下面的东西(四倍经验岂不美哉)
「SPOJ DIVCNT1」Counting Divisors
「SPOJ DIVCNT2」Counting Divisors (square)
「SPOJ DIVCNT3」Counting Divisors (cube)
#include<bits/stdc++.h> #define FILE "read" #define MAXN 100010 using namespace std; typedef unsigned long long ll; ll n,N,K,cnt,prime[MAXN/10],pos[MAXN<<1],g[MAXN<<1]; inline ll read(){ ll x=0,f=1; char ch=getchar(); while(ch>'9'||ch<'0') {if(ch=='-') f=-1; ch=getchar();} while(ch>='0'&&ch<='9') {x=x*10+ch-'0'; ch=getchar();} return x*f; } inline int Id(ll x){return x<=N?x:pos[0]-n/x+1;} inline ll S(ll n,ll j){ if(n<prime[j]) return 0; ll Ans=g[Id(n)]-g[prime[j-1]]; for(int k=j;k<=cnt&&1LL*prime[k]*prime[k]<=n;++k){ ll fac=prime[k]; for(int e=1;1LL*fac*prime[k]<=n;++e){ Ans+=S(n/fac,k+1)*(e*K+1)+(e+1)*K+1; fac=fac*prime[k]; } } return Ans; } void solve(){ n=read(); K=read(); N=sqrt(n); cnt=pos[0]=0; for(ll i=1;i<=n;i=pos[pos[0]]+1) pos[++pos[0]]=n/(n/i); for(int i=1;i<=pos[0];++i) g[i]=pos[i]-1; for(int i=2;i<=N;++i)if(g[i]!=g[i-1]){ prime[++cnt]=i; for(int j=pos[0];1LL*i*i<=pos[j];--j) g[j]=g[j]-(g[Id(pos[j]/i)]-(cnt-1)); } for(int i=1;i<=pos[0];++i) g[i]=g[i]*(K+1); printf("%llu\n",S(n,1)+1); } int main(){ freopen(FILE".in","r",stdin); freopen(FILE".out","w",stdout); int T=read(); while(T--) solve(); return 0; }
我们知道对于质数的情况 ,接下来考虑合数的答案
事实上,埃氏筛每一轮中筛掉的数都是被最小质因子筛掉的,对答案的贡献是当前质数
我们只需要统计每一轮被筛掉的数个数,乘上当前质数就是贡献
这个东西在Min25筛第一部分可以完成
#include<bits/stdc++.h> #define FILE "read" #define MAXN 1200000 using namespace std; typedef unsigned long long ll; ll n,N,Ans,cnt,prime[MAXN/10],g[MAXN<<1],h[MAXN<<1],pos[MAXN<<1]; inline int Id(ll x){return x<=N?x:pos[0]-n/x+1;} inline ll read(){ ll x=0,f=1; char ch=getchar(); while(ch>'9'||ch<'0') {if(ch=='-') f=-1; ch=getchar();} while(ch>='0'&&ch<='9') {x=x*10+ch-'0'; ch=getchar();} return x*f; } void solve(){ n=read(); N=sqrt(n); Ans=cnt=pos[0]=0; for(ll i=1;i<=n;i=pos[pos[0]]+1) pos[++pos[0]]=n/(n/i); for(int i=1;i<=pos[0];++i) g[i]=pos[i]-1; for(int i=1;i<=pos[0];++i) h[i]=(pos[i]&1)?pos[i]*((pos[i]+1)/2)-1:pos[i]/2*(pos[i]+1)-1; for(int i=2;i<=N;++i)if(g[i]!=g[i-1]){ prime[++cnt]=i; Ans+=(g[Id(n/i)]-(cnt-1))*i;//统计合数贡献 for(int j=pos[0];1LL*i*i<=pos[j];--j){ g[j]=g[j]-(g[Id(pos[j]/i)]-g[prime[cnt-1]]); h[j]=h[j]-(h[Id(pos[j]/i)]-h[prime[cnt-1]])*i; } } printf("%llu\n",Ans+h[pos[0]]); } int main(){ freopen(FILE".in","r",stdin); freopen(FILE".out","w",stdout); int T=read(); while(T--) solve(); return 0; }
《一些特殊的数论函数求和问题》——朱震霆