Min_25 筛算法学习笔记
牛客暑期多校连续两场都考了 Min_25 筛,看来不得不学了。
前言
Min_25 筛是一种计算积性函数前缀和的高效算法。
时间复杂度好像是
模板题(洛谷 P5325)
已知某积性函数
首先,我们先化简问题,考虑求幂函数
注,对于幂函数而言,
F(x) 不仅是积性函数,更是完全积性函数。想一想,怎么证明。
先把求和式子按质数、合数分类:
然后枚举最小质因子:
int prime[N],prcnt;
bool is_prime[N];
ll pre1[N],pre2[N];
void get_prime(int n){
fill(is_prime+2,is_prime+n+1,true);
for(int i=2;i<=n;i++){
if (is_prime[i]){
prime[++prcnt]=i;
pre1[prcnt]=(pre1[prcnt-1]+i)%mod;
pre2[prcnt]=(pre2[prcnt-1]+1ll*i*i%mod)%mod;
}
for(int j=1;j<=prcnt;j++){
if (1ll*i*prime[j]>n) break;
is_prime[i*prime[j]]=false;
if (i%prime[j]==0) break;
}
}
}
ll g1[N<<1],g2[N<<1],w[N<<1];
int ind1[N],ind2[N];
ll n;int m,wcnt;
const int inv2=500000004;
const int inv3=333333336;
void get_g_and_ind(){
for(ll l=1;l<=n;){
ll r=n/(n/l);
w[++wcnt]=n/l;
int tmp=(n/l)%mod;
g1[wcnt]=1ll*tmp*(tmp+1)%mod*inv2%mod-1;
g2[wcnt]=1ll*tmp*(tmp+1)%mod*(2*tmp+1)%mod*inv2%mod*inv3%mod-1;
if (n/l<=m) ind1[n/l]=wcnt;
else ind2[r]=wcnt;
l=r+1;
}
for(int i=1;i<=prcnt;i++)
for(int j=1;j<=wcnt;j++){
if (1ll*prime[i]*prime[i]>w[j]) break;
int k=(w[j]/prime[i]<=m)?ind1[w[j]/prime[i]]:ind2[n/(w[j]/prime[i])];
g1[j]=(g1[j]-1ll*prime[i]*(g1[k]-pre1[i-1]+mod)%mod+mod)%mod;
g2[j]=(g2[j]-1ll*prime[i]*prime[i]%mod*(g2[k]-pre2[i-1]+mod)%mod+mod)%mod;
}//这里 g 是可以滚动数组的,最终保留的信息就是 g(n,x)
}
int S(ll x,int y){
if (prime[y]>=x) return 0;
int k=(x<=m)?ind1[x]:ind2[n/x];
int ans=((g2[k]-g1[k]+mod)%mod-(pre2[y]-pre1[y]+mod)%mod+mod)%mod;
for(int i=y+1;i<=prcnt;i++){
if (1ll*prime[i]*prime[i]>x) break;
ll pe=prime[i];
for(int e=1;pe<=x;e++,pe*=prime[i]){
int tmp=pe%mod;
ans=(ans+1ll*tmp*(tmp-1)%mod*(S(x/pe,i)+(e!=1))%mod)%mod;
}
}
return ans;
}
int main(){
cin>>n;
m=sqrt(n);
get_prime(m);
get_g_and_ind();
cout<<(S(n,0)+1)%mod;
return 0;
}
本质理解
虽然 Min_25 筛正宗的用法是用来求积性函数的,但是有些非积性函数的问题也可以用 Min_25 筛来做。
由上面的代码我们可以知道,我们其实不关心
而在
于是,在某些特定的问题中,我们只关心函数在质数的和,或函数在合数的和(两者等价),我们一样可以用 Min_25 筛解决。
一个经典的问题是求某个区间
定义函数
显然这个函数不是积性函数。
但是我们发现
求
可以证明,满足条件的数一定可以写成如下形式:
其中
转化为求
对于
对于合适范围内的
const int N=1e5+100;
#define ll long long
int prime[N],prcnt,pre[N];
bool is_prime[N];
void get_prime(int n){
fill(is_prime+2,is_prime+n+1,true);
for(int i=2;i<=n;i++){
if (is_prime[i]){
prime[++prcnt]=i;
pre[prcnt]=prcnt;
}
for(int j=1;j<=prcnt;j++){
if (1ll*prime[j]*i>n) break;
is_prime[i*prime[j]]=false;
if (i%prime[j]==0) break;
}
}
}
int w[N<<1],ind1[N],ind2[N],wcnt;
ll g[N<<1];int n,m;
void get_g_and_ind(){
for(int l=1;l<=n;){
int tmp=n/l,r=n/tmp;
w[++wcnt]=tmp;
g[wcnt]=tmp-1;
if (tmp<=m) ind1[tmp]=wcnt;
else ind2[r]=wcnt;
l=r+1;
}
for(int i=1;i<=prcnt;i++)
for(int j=1;j<=wcnt;j++){
if (1ll*prime[i]*prime[i]>w[j]) break;
int k=(w[j]/prime[i]<=m)?ind1[w[j]/prime[i]]:ind2[n/(w[j]/prime[i])];
g[j]-=g[k]-pre[i-1];
}
}
ll S(ll x,int y){
if (prime[y]>=x) return 0;
int k=(x<=m)?ind1[x]:ind2[n/x];
return g[k]-pre[y];
}
void init(){
memset(pre,0,sizeof(pre));
memset(g,0,sizeof(g));
memset(ind1,0,sizeof(ind1));
memset(ind2,0,sizeof(ind2));
wcnt=prcnt=0;
}
ll Count(int x){
n=x;
m=sqrt(n);
init();
get_prime(m);
get_g_and_ind();
ll res=0;
for(int i=1;i<=prcnt;i++)
res+=x/prime[i];
for(int i=1;i<=m;i++)
if (x/i>prime[prcnt])
res+=S(x/i,0)-prcnt;
return res;
}
ll calc(ll x){
int r=sqrt(x);
ll ans=Count(r);
int tmp=r+1;
for(int i=2;1ll*i*i<=tmp;i++)
if (tmp%i==0){
if (1ll*(r+1)*(r+1)-i<=x) ans++;
while (tmp%i==0) tmp/=i;
}
if (tmp>1&&1ll*(r+1)*(r+1)-tmp<=x) ans++;
return ans;
}
int main(){
ll l,r;
cin>>l>>r;
cout<<calc(r)-calc(l-1);
return 0;
}