[学习笔记]Min_25筛

注意到我们做反演题时,并不需要用到前缀和中的所有信息,大多数时候只需要用到\(\lfloor\frac n 1\rfloor,\lfloor\frac n 2\rfloor,\lfloor\frac n 3\rfloor......\)这几项即可,而今天我们要讲的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\)的数的数值的和(第二坨)。\((i\in [0,\sqrt n])\)

这里,我们的想法是通过把小质数的贡献删光,从而只留下大质数,所以i的枚举到\(\sqrt n\)为止。

先是1次的部分。

显然有初始条件:\(f[0][j]=\frac{j\times(j+1)}{2}\)

  • 考虑转移:

\(\begin{aligned}f[i+1][j]=f[i][j]-(f[i][\lfloor\frac j {p_{i+1}}\rfloor]-\sum_{k=1}^ik)\times p_{i+1}\end{aligned}\)

  • 来解释一下这个式子:

我们需要筛掉那些最小质因子为\(p_{i+1}\)的数的贡献,也就是dp式中的第二坨,而这就是括号中的部分。括号中后面的\(\sum\) 是为了删去了dp式中的第一坨。(因为质数\(p_{i+1}\)本就在答案中,只要不把它筛掉即可,不需要再在后面另外加)

  • 0次的部分处理类似,这里只给出式子:

\(\begin{aligned}f'[i+1][j]=f'[i][j]-(f'[i][\lfloor\frac{j}{p_{i+1}}\rfloor]-\sum_{k=1}^ii)\times1\end{aligned}\)

最后我们将\(f\)\(f'\)相加,得到\(F\),那么\(F[\sqrt n][k]\)即是小于等于\(k\)的所有质数的贡献。

第二部分

第二部分:小质数的贡献

  • \(g[i][j]\)表示:\(2\)~\(j\)中小于等于\(p_i\)的质数的\(\varphi\)值(我们称其为第一坨),与最小质因子大于\(p_i\)的数的\(\varphi\)的和(第二坨)。\((i\in [0,\sqrt n])\)

有初始条件:\(g[k][j]=F[k][j],k\in[1,\sqrt n]\)

我们刚刚把合数都筛光了,那么现在就要把它们给加回来。

  • 我们枚举每一个小质数质数的次数,考虑转移:

\(\begin{aligned}g[i-1][j]=g[i][j]+\sum_{p_i^{e+1}\le j}[(g[i][\lfloor\frac{j}{p_{i}^e}\rfloor]-\sum_{k=1}^i\varphi(p_k))\times \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。

因为我们只需要求\(\lfloor\frac n 1\rfloor,\lfloor\frac n 2\rfloor,......\),而且在转移时也只会用到这几项,所以我们只需要把这\(\sqrt n\)项标个号即可,这样就把j这一维也降到了\(\sqrt n\),在空间上绝对是妥妥的。

只有当时\(\lfloor\frac n k\rfloor>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\n",(f[1]+1)%mod);
	return 0;
}

posted @ 2020-09-25 20:39  WR_Eternity  阅读(123)  评论(0编辑  收藏  举报