注意到我们做反演题时,并不需要用到前缀和中的所有信息,大多数时候只需要用到(lfloorfrac n 1 floor,lfloorfrac n 2 floor,lfloorfrac n 3 floor......)这几项即可,而今天我们要讲的Min_25筛,就是专门为求出这些位置的前缀和而生的。
既然要求的项数少了,时间复杂度自然也就少了,Min_25的时间复杂度是(varTheta(frac {n^{frac 3 4}}{ln(n)}))的。
大体思路
- Min_25筛在处理时把质数分为大于(sqrt n)与小于(sqrt n)两个部分。
注意到大于(sqrt n)的部分,在计算对于x的贡献时,只会是1次方的,所以我们可以先求出它们对于各前缀和的贡献,而对于小于(sqrt n)的部分,我们则暴力枚举每个质数的次方数即可。
具体实现
这里以求(varphi)的前缀和为例。
第一部分
- 第一部分:大于(sqrt n)的质数(不妨叫它们大质数)的贡献
因为最后每个大质数只会以1次的形式出现,所以我们只需求出((p_i-1))即可。(下面我们将它们分为1次与0次两部分求解)
- (f[i][j])表示:(2)~(j)中小于等于(p_i)的质数的数值(我们称其为第一坨),与最小质因子大于(p_i)的数的数值的和(第二坨)。((iin [0,sqrt n]))
这里,我们的想法是通过把小质数的贡献删光,从而只留下大质数,所以i的枚举到(sqrt n)为止。
先是1次的部分。
显然有初始条件:(f[0][j]=frac{j imes(j+1)}{2})
- 考虑转移:
(egin{aligned}f[i+1][j]=f[i][j]-(f[i][lfloorfrac j {p_{i+1}} floor]-sum_{k=1}^ik) imes p_{i+1}end{aligned})
- 来解释一下这个式子:
我们需要筛掉那些最小质因子为(p_{i+1})的数的贡献,也就是dp式中的第二坨,而这就是括号中的部分。括号中后面的(sum) 是为了删去了dp式中的第一坨。(因为质数(p_{i+1})本就在答案中,只要不把它筛掉即可,不需要再在后面另外加)
- 0次的部分处理类似,这里只给出式子:
(egin{aligned}f'[i+1][j]=f'[i][j]-(f'[i][lfloorfrac{j}{p_{i+1}} floor]-sum_{k=1}^ii) imes1end{aligned})
最后我们将(f)与(f')相加,得到(F),那么(F[sqrt n][k])即是小于等于(k)的所有质数的贡献。
第二部分
第二部分:小质数的贡献
- (g[i][j])表示:(2)~(j)中小于等于(p_i)的质数的(varphi)值(我们称其为第一坨),与最小质因子大于(p_i)的数的(varphi)的和(第二坨)。((iin [0,sqrt n]))
有初始条件:(g[k][j]=F[k][j],kin[1,sqrt n])
我们刚刚把合数都筛光了,那么现在就要把它们给加回来。
- 我们枚举每一个小质数质数的次数,考虑转移:
(egin{aligned}g[i-1][j]=g[i][j]+sum_{p_i^{e+1}le j}[(g[i][lfloorfrac{j}{p_{i}^e} floor]-sum_{k=1}^ivarphi(p_k)) imes varphi(p_i^e)+varphi(p_i^{e+1})]end{aligned})
- 解释一下式子:
因为p每多1次,对于答案的影响都不一样(第一次是乘p-1,后来是乘p),所以我们需要枚举次数,求出每一个(p^e)对于答案的贡献。大体思路和之前的相似,只是因为这次我们是要往里面加,所以需要在后面挂上(p^{e+1})的贡献。
一些细节
第一部分时从小到大枚举i,第二部分从大到小枚举i。
为了在内存上去掉i这一维,我们需要从大到小枚举j。
因为我们只需要求(lfloorfrac n 1 floor,lfloorfrac n 2 floor,......),而且在转移时也只会用到这几项,所以我们只需要把这(sqrt n)项标个号即可,这样就把j这一维也降到了(sqrt n),在空间上绝对是妥妥的。
只有当时(lfloorfrac n k floor>p_i)才进行转移,不然的话一减后面那坨(sum)必定等于0。((*))
显然,括号中的那坨(sum)能预处理。
- 关于时间复杂度:
有了“优化((*))”,我们就能把原先看上去是(varTheta(frac{n}{ln(n)}))的算法变成(varTheta(frac{n^{frac 3 4}}{ln(n)})),但是我太菜了,并不会具体的证明。。。。
- 洛谷模板题的代码:
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn=1e6+5,mod=1e9+7;
const ll inv6=166666668%mod;
int vis[maxn],p[maxn],id1[maxn],id2[maxn],tot;
ll sum1[maxn],sum2[maxn],f[maxn],g1[maxn],g2[maxn],to[maxn],n,sqrtn,tt;
void sieve(int N){
vis[1]=1;
for (int i=2;i<=N;i++){
if (!vis[i]) p[++p[0]]=i,sum1[p[0]]=(sum1[p[0]-1]+i)%mod,sum2[p[0]]=(sum2[p[0]-1]+1ll*i*i%mod)%mod;
for (int j=1;j<=p[0]&&i*p[j]<=N;j++){
vis[i*p[j]]=1;
if (i%p[j]==0) break;
}
}
}
int getid(ll x) {return x<=sqrtn?id1[x]:id2[n/x];}
ll Mod(ll x) {return (x%mod+mod)%mod;}
int main(){
scanf("%lld",&n);
sieve(sqrtn=sqrt(n));
for (ll l=1,r;l<=n;l=r+1){
r=n/(n/l);
ll w=n/l;
to[++tot]=w;
if (w<=sqrtn) id1[w]=tot; else id2[n/w]=tot;
w%=mod;
g1[tot]=w*(w+1)/2%mod;
g2[tot]=w*(w+1)%mod*(2ll*w+1)%mod*inv6%mod;
g1[tot]=(g1[tot]-1+mod)%mod;
g2[tot]=(g2[tot]-1+mod)%mod;
}
for (int i=1;i<=p[0];i++)
for (int j=1;j<=tot&&to[j]>=1ll*p[i]*p[i];j++){
int k=getid(to[j]/p[i]);
g1[j]=(g1[j]-Mod(g1[k]-sum1[i-1])*p[i]%mod+mod)%mod;
g2[j]=(g2[j]-Mod(g2[k]-sum2[i-1])*p[i]%mod*p[i]%mod+mod)%mod;
}
for (int i=1;i<=tot;i++) f[i]=Mod(g2[i]-g1[i]);
for (int i=p[0];i>=1;i--)
for (int j=1;j<=tot&&to[j]>=1ll*p[i]*p[i];j++)
for (ll t=p[i];t*p[i]<=to[j];t*=p[i]){
int k=getid(to[j]/t);
tt=t%mod;
f[j]=(f[j]+Mod(f[k]-sum2[i]+sum1[i])*tt%mod*Mod(tt-1)%mod+tt*p[i]%mod*Mod(tt*p[i]%mod-1)%mod)%mod;
}
printf("%lld
",(f[1]+1)%mod);
return 0;
}