Min-25 筛学习笔记
一、简要介绍
Min-25 筛,是一种能在亚线性时间内求出特定的一类积性函数
具体来说,Min-25 筛可以在
一般来说,积性函数
在质数处可以被表示成一个较为简单的多项式。 在质数的若干次幂( 处)的点值可以 计算。
一般来说,在
二、算法过程
以下内容均用
考虑拆分出每个
由于要根据最小质因子讨论,因此记
先不管质数的情况,设
其中
假设
接下来考虑计算
根据要求,
设
考虑
否则,计算的数一定有最小质因子
减去的项是去掉有
同样在递归的过程中也只会访问到所有的
边界条件是
拓展 – 非递归版本的实现
对于
但假如我们想和杜教筛一样,在复杂度不变的情况下求出
最简单的想法是在递归求
注意到在处理
我们认为
依然考虑
但是注意到最后一项求和式并不是很好看,考虑化简,注意到当
因此得到
从大到小枚举
其中边界条件为
有了这个 Min-25 筛的实现后,我们可以解决形如
那么我们根据整除分块枚举
三、代码实现
以线性筛模板题为例(Link)下面的代码是递归的实现:
#include<bits/stdc++.h> #define int long long using namespace std; const int MAXN=2e5+1,MOD=1e9+7; bool isco[MAXN]; int p[MAXN],tot; int val[MAXN]; //需要处理的 n (降序排列) int idx1[MAXN],idx2[MAXN]; //用于储存每个可能的 n/t 对应的下标 int g1[MAXN],g2[MAXN]; //p,p^2 在所有 n/t 处的前缀和 int fp1[MAXN],fp2[MAXN]; //p,p^2 在所有 <=sqrt(n) 的质数处的前缀和 int n,m,B; inline int idx(int v) { //v 对应存储在数组里的下标 return (v<=B)?idx1[v]:idx2[n/v]; } inline int S(int n,int k) { if(p[k]>n) return 0; int ans=((g2[idx(n)]+MOD-g1[idx(n)])%MOD+MOD-(fp2[k]+MOD-fp1[k])%MOD)%MOD; for(int i=k+1;i<=tot&&p[i]*p[i]<=n;++i) { for(int c=1,v=p[i];v<=n;++c,v*=p[i]) { //直接递归求值 ans=(ans+(v-1)%MOD*(v%MOD)%MOD*((c>1)+S(n/v,i))%MOD)%MOD; } } return ans; } signed main() { scanf("%lld",&n),B=sqrt(n); for(int i=2;i<=B;++i) { if(!isco[i]) p[++tot]=i; for(int j=1;j<=tot&&i*p[j]<=B;++j) { isco[i*p[j]]=true; if(i%p[j]==0) break; } //线性筛预处理质数 } for(int i=1;i<=tot;++i) { //在 <=sqrt(n) 的质数处的前缀和 fp1[i]=(fp1[i-1]+p[i])%MOD; fp2[i]=(fp2[i-1]+p[i]*p[i]%MOD)%MOD; } for(int l=1,r;l<=n;l=r+1) { //整除分块预处理出点值 r=n/(n/l),val[++m]=n/l; if(val[m]<=B) idx1[val[m]]=m; else idx2[n/val[m]]=m; } for(int i=1;i<=m;++i) { int k=val[i]%MOD; g1[i]=(MOD+1)/2*k%MOD*(k+1)%MOD; g2[i]=(MOD+1)/6*k%MOD*(2*k+1)%MOD*(k+1)%MOD; g1[i]=g1[i]?g1[i]-1:MOD-1,g2[i]=g2[i]?g2[i]-1:MOD-1; //G(n,0) 的值 } for(int k=1;k<=tot;++k) { for(int i=1;i<=m&&p[k]*p[k]<=val[i];++i) { //暴力枚举, 按递推式计算 g1[i]=(g1[i]+MOD-p[k]*(g1[idx(val[i]/p[k])]+MOD-fp1[k-1])%MOD)%MOD; g2[i]=(g2[i]+MOD-p[k]*p[k]%MOD*(g2[idx(val[i]/p[k])]+MOD-fp2[k-1])%MOD)%MOD; } } printf("%lld\n",(S(n,0)+1)%MOD); return 0; }
下面的代码是非递归的实现:
#include<bits/stdc++.h> #define int long long using namespace std; const int MAXN=2e5+1,MOD=1e9+7; bool isco[MAXN]; int p[MAXN],tot; int val[MAXN]; int idx1[MAXN],idx2[MAXN]; int g1[MAXN],g2[MAXN]; int fp1[MAXN],fp2[MAXN]; int S[MAXN]; int n,m,B; inline int idx(int v) { return (v<=B)?idx1[v]:idx2[n/v]; } signed main() { scanf("%lld",&n),B=sqrt(n); for(int i=2;i<=B;++i) { if(!isco[i]) p[++tot]=i; for(int j=1;j<=tot&&i*p[j]<=B;++j) { isco[i*p[j]]=true; if(i%p[j]==0) break; } } for(int i=1;i<=tot;++i) { fp1[i]=(fp1[i-1]+p[i])%MOD; fp2[i]=(fp2[i-1]+p[i]*p[i]%MOD)%MOD; } for(int l=1,r;l<=n;l=r+1) { r=n/(n/l),val[++m]=n/l; if(val[m]<=B) idx1[val[m]]=m; else idx2[n/val[m]]=m; } for(int i=1;i<=m;++i) { int k=val[i]%MOD; g1[i]=(MOD+1)/2*k%MOD*(k+1)%MOD; g2[i]=(MOD+1)/6*k%MOD*(2*k+1)%MOD*(k+1)%MOD; g1[i]=g1[i]?g1[i]-1:MOD-1,g2[i]=g2[i]?g2[i]-1:MOD-1; } for(int k=1;k<=tot;++k) { for(int i=1;i<=m&&p[k]*p[k]<=val[i];++i) { g1[i]=(g1[i]+MOD-p[k]*(g1[idx(val[i]/p[k])]+MOD-fp1[k-1])%MOD)%MOD; g2[i]=(g2[i]+MOD-p[k]*p[k]%MOD*(g2[idx(val[i]/p[k])]+MOD-fp2[k-1])%MOD)%MOD; } } for(int i=1;i<=m;++i) S[i]=(g2[i]+MOD-g1[i])%MOD; for(int k=tot;k>=1;--k) { for(int i=1;i<=m&&p[k]*p[k]<=val[i];++i) { for(int c=1,v=p[k];v*p[k]<=val[i];) { //根据递推式计算 S[i]=(S[i]+(v-1)%MOD*(v%MOD)%MOD*(S[idx(val[i]/v)]+MOD-(fp2[k]-fp1[k]))%MOD)%MOD; ++c,v*=p[k],S[i]=(S[i]+(v-1)%MOD*(v%MOD)%MOD)%MOD; } } } printf("%lld\n",(S[idx(n)]+1)%MOD); return 0; }
实际运行上,非递归版本的实现会慢于递归版的实现。
四、复杂度分析
空间复杂度
时间复杂度可以看做合法的
因此复杂度可以估计为:
五、例题
I. [SPOJ-34096] DIVCNTK
题目大意
给定
,求 (即 的因子个数)。 数据范围:
。
思路分析
容易证明
时间复杂度
代码呈现
#include<bits/stdc++.h> #define int unsigned long long using namespace std; const int MAXN=2e5+1; bool isco[MAXN]; int p[MAXN],tot; int idx1[MAXN],idx2[MAXN],val[MAXN]; int g[MAXN]; inline void solve() { int n,K,m=0,B; scanf("%llu%llu",&n,&K),B=sqrt(n); for(int l=1,r;l<=n;l=r+1) { r=n/(n/l),val[++m]=n/l; if(val[m]<=B) idx1[val[m]]=m; else idx2[n/val[m]]=m; g[m]=val[m]-1; } auto idx=[&](int v) { return v<=B?idx1[v]:idx2[n/v]; }; for(int k=1;k<=tot;++k) { for(int i=1;i<=m&&p[k]*p[k]<=val[i];++i) { g[i]-=g[idx(val[i]/p[k])]-(k-1); } } auto S=[&](auto self,int n,int k) -> int { if(n<=p[k]) return 0; int ans=(g[idx(n)]-k)*(K+1); for(int i=k+1;i<=tot&&p[i]*p[i]<=n;++i) { for(int c=1,v=p[i];v<=n;++c,v*=p[i]) { ans+=(c*K+1)*(self(self,n/v,i)+(c>1)); } } return ans; }; printf("%llu\n",S(S,n,0)+1); } signed main() { for(int i=2;i<MAXN;++i) { if(!isco[i]) p[++tot]=i; for(int j=1;j<=tot&&i*p[j]<MAXN;++j) { isco[i*p[j]]=true; if(i%p[j]==0) break; } } int T; scanf("%llu",&T); while(T--) solve(); return 0; }
II. [洛谷-4213] SUM
题目大意
求
的前缀和。 数据范围:
。
思路分析
注意到
时间复杂度
代码呈现
#include<bits/stdc++.h> #define int long long using namespace std; const int MAXN=2e5+1; bool isco[MAXN]; int p[MAXN],tot,val[MAXN],fp1[MAXN]; int idx1[MAXN],idx2[MAXN],g0[MAXN],g1[MAXN]; inline void solve() { int n,m=0; scanf("%lld",&n); int B=sqrt(n); tot=0; for(int i=2;i<=B;++i) { if(!isco[i]) p[++tot]=i,fp1[tot]=i+fp1[tot-1]; for(int j=1;j<=tot&&p[j]*i<=B;++j) { isco[p[j]*i]=true; if(i%p[j]==0) break; } } for(int l=1,r;l<=n;l=r+1) { r=n/(n/l),val[++m]=n/l; if(val[m]<=B) idx1[val[m]]=m; else idx2[n/val[m]]=m; g0[m]=val[m]-1,g1[m]=val[m]*(val[m]+1)/2-1; } auto idx=[&](int v) { return (v<=B)?idx1[v]:idx2[n/v]; }; for(int k=1;k<=tot;++k) { for(int i=1;i<=m&&p[k]*p[k]<=val[i];++i) { g0[i]-=g0[idx(val[i]/p[k])]-(k-1); g1[i]-=p[k]*(g1[idx(val[i]/p[k])]-fp1[k-1]); } } auto mu=[&](auto self,int n,int k) -> int { if(n<=p[k]) return 0; int ans=k-g0[idx(n)]; for(int i=k+1;i<=tot&&p[i]*p[i]<=n;++i) { ans-=self(self,n/p[i],i); } return ans; }; auto phi=[&](auto self,int n,int k) -> int { if(n<=p[k]) return 0; int ans=g1[idx(n)]-g0[idx(n)]-(fp1[k]-k); for(int i=k+1;i<=tot&&p[i]*p[i]<=n;++i) { for(int c=1,v=p[i];v<=n;++c,v*=p[i]) { ans+=(v-v/p[i])*(self(self,n/v,i)+(c>1)); } } return ans; }; printf("%lld %lld\n",phi(phi,n,0)+1,mu(mu,n,0)+1); } signed main() { int T; scanf("%lld",&T); while(T--) solve(); return 0; }
* III. [HDU-6417] Rikka with APSP
题目大意
给定一张
个点的无向完全图, 之间的边权定义为最小的正整数 使得 是完全平方数。 求
的值,其中 表示 之间的最短路长度。 数据范围:
。
思路分析
先把每个数
那么
因此考虑拆贡献,对于某个质数
容易发现
对于
因此我们只预处理在所有
容易发现这是一个标准的 Min-25 筛的第一部分,因此直接用 Min-25 筛处理即可。
这一部分的时间复杂度为
但是我们还漏掉了一些贡献,对于
设公比最简分数为
然后考虑
因此这部分的答案就是
将答案分成三部分分别处理即可。
第三部分的另一种处理方法
对于这样乘积是完全平方数的数对计数,最朴素的思路就是枚举
考虑刻画
考虑整除分块枚举
由于
两种做法时间复杂度均为
代码呈现
实现方式一:
#include<bits/stdc++.h> #define int long long #define LL __int128 using namespace std; const int MAXN=2e5+1,MOD=998244353; bool isco[MAXN]; int p[MAXN],tot,val[MAXN],fp[MAXN],phi[MAXN]; int idx1[MAXN],idx2[MAXN],g[MAXN]; inline void solve() { int n,m=0; scanf("%lld",&n); int B=sqrt(n); for(int l=1,r;l<=n;l=r+1) { r=n/(n/l),val[++m]=n/l; if(val[m]<=B) idx1[val[m]]=m; else idx2[n/val[m]]=m; } auto idx=[&](int v) { return (v<=B)?idx1[v]:idx2[n/v]; }; for(int i=1;i<=m;++i) { int k=val[i]%MOD; g[i]=(k*(k+1)/2+MOD-1)%MOD; } for(int k=1;k<=tot;++k) { for(int i=1;i<=m&&p[k]*p[k]<=val[i];++i) { g[i]=(g[i]+MOD-p[k]*(g[idx(val[i]/p[k])]+MOD-fp[k-1])%MOD)%MOD; } } int ans=0; for(int i=1;i<=tot&&p[i]*p[i]<=n;++i) { int s=0; for(int v=p[i],c=1;v<=n;v*=p[i],c=-c) s+=c*(n/v); ans=(ans+(n-s)%MOD*(s%MOD)%MOD*p[i]%MOD)%MOD; } for(int l=1,r;l<=n;l=r+1) { r=n/(n/l); int k=n/l; if((LL)l*l>n) { ans=(ans+(n-k)%MOD*(k%MOD)%MOD*(g[idx(r)]+MOD-g[idx(l-1)])%MOD)%MOD; } } for(int i=2;i*i<=n;++i) ans=(ans+(n/(i*i))%MOD*phi[i]%MOD)%MOD; printf("%lld\n",ans); } signed main() { for(int i=2;i<MAXN;++i) { if(!isco[i]) p[++tot]=i,phi[i]=i-1; for(int j=1;j<=tot&&p[j]*i<MAXN;++j) { isco[p[j]*i]=true,phi[p[j]*i]=phi[i]*(p[j]-1); if(i%p[j]==0) { phi[p[j]*i]=phi[i]*p[j]; break; } } } for(int i=1;i<=tot;++i) fp[i]=(fp[i-1]+p[i])%MOD; int T; scanf("%lld",&T); while(T--) solve(); return 0; }
实现方式二:
#include<bits/stdc++.h> #define int long long #define LL __int128 using namespace std; const int MAXN=2e5+1,MOD=998244353; bool isco[MAXN]; int p[MAXN],tot,val[MAXN],fp1[MAXN]; int idx1[MAXN],idx2[MAXN],g0[MAXN],g1[MAXN],S[MAXN]; inline void solve() { int n,m=0; scanf("%lld",&n); int B=sqrt(n); tot=0; for(int i=2;i<=B;++i) { if(!isco[i]) p[++tot]=i; for(int j=1;j<=tot&&p[j]*i<=B;++j) { isco[p[j]*i]=true; if(i%p[j]==0) break; } } for(int i=1;i<=tot;++i) fp1[i]=(fp1[i-1]+p[i])%MOD; for(int l=1,r;l<=n;l=r+1) { r=n/(n/l),val[++m]=n/l; if(val[m]<=B) idx1[val[m]]=m; else idx2[n/val[m]]=m; int k=val[m]%MOD; g0[m]=k-1,g1[m]=(k*(k+1)/2+MOD-1)%MOD; } auto idx=[&](int v) { return (v<=B)?idx1[v]:idx2[n/v]; }; for(int k=1;k<=tot;++k) { for(int i=1;i<=m&&p[k]*p[k]<=val[i];++i) { g0[i]=(g0[i]+MOD-(g0[idx(val[i]/p[k])]-(k-1)))%MOD; g1[i]=(g1[i]+MOD-p[k]*(g1[idx(val[i]/p[k])]+MOD-fp1[k-1])%MOD)%MOD; } } for(int i=1;i<=m;++i) S[i]=g0[i]; for(int k=tot;k>=1;--k) { for(int i=1;i<=m&&p[k]*p[k]<=val[i];++i) { S[i]=(S[i]+MOD+S[idx(val[i]/p[k])]-k)%MOD; } } for(int i=1;i<=m;++i) S[i]=(S[i]+1)%MOD; int ans=0; for(int i=1;i<=tot&&p[i]*p[i]<=n;++i) { int s=0; for(int v=p[i],c=1;v<=n;v*=p[i],c=-c) s+=c*(n/v); ans=(ans+(n-s)%MOD*(s%MOD)%MOD*p[i]%MOD)%MOD; } for(int l=1,r;l<=n;l=r+1) { r=n/(n/l); int k=n/l; if((LL)l*l>n) { ans=(ans+(n-k)%MOD*(k%MOD)%MOD*(g1[idx(r)]+MOD-g1[idx(l-1)])%MOD)%MOD; } } for(int l=1,Q=B+5,r;l<=n;l=r+1) { r=n/(n/l); int k=n/l; while(Q*Q>k) --Q; //Q = sqrt(k) ans=(ans+(Q*(Q-1)/2)%MOD*(S[idx(r)]+MOD-S[idx(l-1)])%MOD)%MOD; } printf("%lld\n",ans); } signed main() { int T; scanf("%lld",&T); while(T--) solve(); return 0; }
__EOF__