简介
min25筛能在在亚线性时间复杂度求出一类积性函数的前缀和,前提是这个积性函数在质数和质数的幂等位置的函数值比较好求。它借助埃氏筛的思想,将原问题转化为在质因子处的相似的子问题,从而得到一个递推式,达到快速求解的目的。
算法原理
一些符号
为了方便描述,定义以下符号:
设我们的问题是求
F
(
n
)
∑
i
=
1
n
f
(
i
)
F(n)\sum\limits_{i=1}^nf(i)
F(n)i=1∑nf(i),假设
f
(
i
)
f(i)
f(i)是一个完全积性函数。
- m i n p ( i ) minp(i) minp(i),表示 i i i的最小质因子
- p r k pr_k prk,表示 n n n以内第 k k k个质数
- ∣ p r ( n ) ∣ |pr(n)| ∣pr(n)∣,表示 n n n以内质数的个数
g函数
g ( n , i ) g(n,i) g(n,i)表示在埃氏筛中,在 n n n以内前 i i i个质数筛完后,剩下的数的 f f f值之和。剩下的数包含所有质数,以及最小质因子大于 p r i pr_i pri的合数。
g ( n , i ) = ∑ ( j ∈ p r i m e ) ∧ ( m i n p ( j ) > p r i ) f ( j ) g(n,i)=\sum\limits_{(j\in prime)\wedge(minp(j)>pr_i)}f(j) g(n,i)=(j∈prime)∧(minp(j)>pri)∑f(j)
由此可知 g ( n , ∣ p r ( n ) ∣ ) g(n,|pr(n)|) g(n,∣pr(n)∣)表示做完埃氏筛后未被删掉的数(即 n n n以内所有质数)的 f f f值之和, g ( p r k , k ) g(pr_k,k) g(prk,k)表示前 k k k个质数的 f f f值之和。
我们可以用如下递推式求 g ( n , i ) g(n,i) g(n,i)。
g ( n , i ) = { g ( n , i − 1 ) p r i 2 > n g ( n , i − 1 ) − f ( p r i ) × ( g ( ⌊ n p r i ⌋ , i − 1 ) − g ( p r i − 1 , i − 1 ) ) p r i 2 ≤ n g(n,i)= \begin{aligned} \left\{\begin{matrix} g(n,i-1)\qquad &pr_i^2>n \\ \\ g(n,i-1)-f(pr_i)\times(g(\lfloor\dfrac{n}{pr_i}\rfloor,i-1)-g(pr_{i-1},i-1)) \qquad &pr_i^2\leq n \end{matrix}\right. \end{aligned} g(n,i)=⎩ ⎨ ⎧g(n,i−1)g(n,i−1)−f(pri)×(g(⌊prin⌋,i−1)−g(pri−1,i−1))pri2>npri2≤n
对于第一部分,因为 p r i 2 > n pr_i^2>n pri2>n,所以在埃氏筛中 p r i pr_i pri无法筛掉任何一个数,所以等于上一个状态。在埃氏筛中,一个合数只会被它的最小质因数筛掉,而质数不会被筛。
对于第二部分, g ( n , i ) g(n,i) g(n,i)所包含的 f f f值,等于 g ( n , i − 1 ) g(n,i-1) g(n,i−1)所包含的 f f f值减去 p r i pr_i pri筛掉的数的 f f f值,即减去最小质因子为 p r i pr_i pri的合数的 f f f值。
我们发现这个式子也可以写成含 g g g的表达式,即 f ( p r i ) × ( g ( ⌊ n p r i ⌋ , i − 1 ) − g ( p r i − 1 , i − 1 ) ) f(pr_i)\times(g(\lfloor\dfrac{n}{pr_i}\rfloor,i-1)-g(pr_{i-1},i-1)) f(pri)×(g(⌊prin⌋,i−1)−g(pri−1,i−1))。其中 g ( ⌊ n p r i ⌋ , i − 1 ) g(\lfloor\dfrac{n}{pr_i}\rfloor,i-1) g(⌊prin⌋,i−1)是 ⌊ n p r i ⌋ \lfloor\dfrac{n}{pr_i}\rfloor ⌊prin⌋以内用前 i − 1 i-1 i−1个质数筛过后的 f f f值之和,它包含两个部分:
- 最小质因子大于 p r i − 1 pr_{i-1} pri−1的数的 f f f值
- 前 i − 1 i-1 i−1个质数的 f f f值,即 g ( p r i − 1 , i − 1 ) g(pr_{i-1},i-1) g(pri−1,i−1)
那么减掉第二部分后,得到的是 ⌊ n p r i ⌋ \lfloor\dfrac{n}{pr_i}\rfloor ⌊prin⌋以内最小质因子大于 p r i − 1 pr_{i-1} pri−1的数的 f f f值。如果再乘上 p r i pr_i pri,得到的是 n n n以内最小质因子等于 p r i pr_i pri的合数的 f f f值之和,这部分就是 p r i pr_i pri筛去的数的 f f f值之和。
但是只有当 f f f为完全积性函数时, f ( p r i ) f(pr_i) f(pri)才能提取出来。而这里 f f f是积性函数,却不一定是完全积性函数,我们怎么能够这样推出式子呢?而且,这个递推式的初始条件是 g ( n , 0 ) g(n,0) g(n,0),而 g ( n , 0 ) g(n,0) g(n,0)正是题目要求的,怎么可以用来做初始值呢?
其实在这里的 g ( n , 0 ) g(n,0) g(n,0)所求的并不是真正的 f f f值之和,它求的是另一个完全积性函数的和,假设这个函数为 f ′ f' f′。在质数位置和质数幂的位置, f ′ f' f′值与 f f f值相等;而在其他位置, f ′ f' f′值与 f f f值不一定相等。设置 f ′ f' f′的目的,是因为这里需要完全积性函数。用 f ′ f' f′代替 f f f,即可使递推式成立。
下文要求的 g g g值中只包含 f f f在质数和质数幂处的值,其他地方的值都被筛掉了,所以求出的答案是正确的。
递推式已经推出来了,接下来看看如何实现。
g
g
g函数有两个参数直接做的话,时间复杂度和空间复杂度都很大。我们发现第二维是可以滚动的,所以空间上可以省掉第二维。
令
s
i
=
g
(
p
r
i
,
i
)
s_i=g(pr_i,i)
si=g(pri,i),则
s
i
s_i
si可以在求质数时求出。又因为
⌊
⌊
n
a
⌋
b
⌋
=
⌊
n
a
b
⌋
\lfloor\dfrac{\lfloor\frac na\rfloor}{b}\rfloor=\lfloor\dfrac{n}{ab}\rfloor
⌊b⌊an⌋⌋=⌊abn⌋,所以在递推中所有
g
g
g值的参数都在数论分块的
2
n
2\sqrt n
2n个值之中。所以我们一开始将这
2
n
2\sqrt n
2n个数先存起来,然后在上面进行滚动求值。
code
void init(){
x=sqrt(n)+1;
dd();//埃氏筛
for(long long l=1,r;l<=n;l=r+1){
r=n/(n/l);
v[++vt]=n/l;//记下数论分块中可能用到的值
if(n/l<=x) f[0][n/l]=vt;
else f[1][l]=vt;
long long t=n/l;
g[vt]=...;//求g(v[vt],0)处的值
}
long long w;
for(int i=1;i<=p1;i++){
w=1ll*pr[i]*pr[i];
long long t;
for(int j=1;j<=vt&&w<=v[j];j++){
t=v[j]/pr[i];
if(t<=x) t=f[0][t];
else t=f[1][n/t];
g[j]=(...);//求g(j,i)的值
}
}//滚动求g(n,i)
}
S函数
设 S ( n , i ) S(n,i) S(n,i)表示 n n n以内的最小质因子大于 p r i pr_i pri的数的 f f f值之和,则有
S ( n , i ) = g ( n , ∣ p r ( n ) ∣ ) − g ( p r i , ∣ p r ( n ) ∣ ) + ∑ j > i ∑ p r j k ≤ n f ( p r j k ) × ( S ( ⌊ n p r j k ⌋ , j ) + [ k > 1 ] ) S(n,i)=g(n,|pr(n)|)-g(pr_i,|pr(n)|)+\sum\limits_{j>i}\sum\limits_{pr_j^k\leq n}f(pr_j^k)\times (S(\lfloor\dfrac{n}{pr_j^k}\rfloor,j)+[k>1]) S(n,i)=g(n,∣pr(n)∣)−g(pri,∣pr(n)∣)+j>i∑prjk≤n∑f(prjk)×(S(⌊prjkn⌋,j)+[k>1])
其中 g ( n , ∣ p r ( n ) ∣ ) − g ( p r i , ∣ p r ( n ) ∣ ) g(n,|pr(n)|)-g(pr_i,|pr(n)|) g(n,∣pr(n)∣)−g(pri,∣pr(n)∣)表示 n n n以内大于 p r i pr_i pri的质数的 f f f值之和。后面相当于求 n n n以内不是质数的最小质因子大于 p r i pr_i pri的数的 f f f值之和。当 k = 1 k=1 k=1时, f ( p r j ) f(pr_j) f(prj)在前面已经被计算过了,所以不用加1;当 k > 1 k>1 k>1时, p r j k pr_j^k prjk不是质数,所以要加上。
S S S的初始值为 S ( n , ∣ p r ( n ) ∣ ) = 0 S(n,|pr(n)|)=0 S(n,∣pr(n)∣)=0。
我们要求的是 S ( n , 0 ) + f ( 1 ) S(n,0)+f(1) S(n,0)+f(1)。
观察上述的递推式,前两项已经求出。最后一项有两个求和号,分别枚举质因数和它的幂数。
当 p r j 2 > n pr_j^2>n prj2>n时,在 k = 1 k=1 k=1时 S ( ⌊ n p r j k ⌋ , j ) S(\lfloor\dfrac{n}{pr_j^k}\rfloor,j) S(⌊prjkn⌋,j)显然为 0 0 0( ⌊ n p r j k ⌋ < p r j \lfloor\dfrac{n}{pr_j^k}\rfloor<pr_j ⌊prjkn⌋<prj,不可能有数在 ⌊ n p r j k ⌋ < p r j \lfloor\dfrac{n}{pr_j^k}\rfloor<pr_j ⌊prjkn⌋<prj以内且最小质因子大于 p r j pr_j prj)。所以我们只要求 n \sqrt n n以内的质因子。而 S ( ⌊ n p r j k ⌋ , j ) S(\lfloor\dfrac{n}{pr_j^k}\rfloor,j) S(⌊prjkn⌋,j)中第一个参数也只能取数论分块的 2 n 2\sqrt n 2n个值。
code
long long S(long long p,int q){
if(pr[q]>=p) return 0;
long long td=(p<=x?f[0][p]:f[1][n/p]);
long long re=((g[td]-s[q])%mod+mod)%mod;//前两项
for(int j=q+1;j<=p1;j++){
if(1ll*pr[j]*pr[j]>p) break;
for(long long o=1,pq=pr[j];pq<=p;++o,pq=pq*pr[j]){
re=(re+1ll*(...)%mod*(S(p/pq,j)%mod+(o>1))%mod)%mod;//最后一项
}
}
return re;
}
思考
观察 S ( n , i ) S(n,i) S(n,i)的递推式,将其改成如下式子
S ( n , i ) = ∑ j > i ∑ p r j k ≤ n f ( p r j k ) × ( S ( ⌊ n p r j k ⌋ , j ) + 1 ) S(n,i)=\sum\limits_{j>i}\sum\limits_{pr_j^k\leq n}f(pr_j^k)\times (S(\lfloor\dfrac{n}{pr_j^k}\rfloor,j)+1) S(n,i)=j>i∑prjk≤n∑f(prjk)×(S(⌊prjkn⌋,j)+1)
递推式意义不变,而且不用求 g g g函数了。那为什么我们不能用这个式子来求 S S S呢?
注意当 + [ k > 1 ] +[k>1] +[k>1]变为 + 1 +1 +1时,若 k = 1 k=1 k=1, p r j 2 > n pr_j^2>n prj2>n,则 p r j pr_j prj也有贡献,这就要求我们求出 n n n以内的所有质数。一般的 n n n都是 1 0 9 10^9 109以上的级别的,这显然是不可行的。
总结
min25筛的时间复杂度为 O ( n 3 4 ln n ) O(\dfrac{n^{\frac 34}}{\ln n}) O(lnnn43),相对于杜教筛来说,min25筛要快一些,而且不用找 g , h g,h g,h以及求它们的前缀和。
有时候题目中的 f f f是一个多项式,我们需要把它拆开,对每一项都用 g g g的递推式求一次,最后再求和。
例题
f ( p k ) = p k ( p k − 1 ) = p 2 k − p k f(p^k)=p^k(p^k-1)=p^{2k}-p^k f(pk)=pk(pk−1)=p2k−pk,令 f 1 ( x ) = x 2 f_1(x)=x^2 f1(x)=x2, f 2 ( x ) = x f_2(x)=x f2(x)=x,则 f ( p k ) = f 1 ( p k ) + f 2 ( p k ) f(p^k)=f_1(p^k)+f_2(p^k) f(pk)=f1(pk)+f2(pk)。因为 f 1 f_1 f1和 f 2 f_2 f2都是完全积性函数,所以我们可以分别将 f 1 , f 2 f_1,f_2 f1,f2带入 g g g的递推式中,做min25筛。
code
#include<bits/stdc++.h>
using namespace std;
const int N=200000;
const long long mod=1000000007;
int p1,vt,z[N+5],pr[N+5];
long long n,x,ny,v[N+5],f[2][N+5],s1[N+5],s2[N+5],g1[N+5],g2[N+5];
long long mi(long long t,long long v){
if(!v) return 1;
long long re=mi(t,v/2);
re=re*re%mod;
if(v&1) re=re*t%mod;
return re;
}
void dd(){
for(int i=2;i<=N;i++){
if(!z[i]){
pr[++p1]=i;
s1[p1]=(s1[p1-1]+1ll*i*i%mod)%mod;
s2[p1]=(s2[p1-1]+i)%mod;
}
for(int j=1;j<=p1&&i*pr[j]<=N;j++){
z[i*pr[j]]=1;
if(i%pr[j]==0) break;
}
}
}
void init(){
x=sqrt(n)+1;
ny=mi(6ll,mod-2);
dd();
for(long long l=1,r;l<=n;l=r+1){
r=n/(n/l);
v[++vt]=n/l;
if(n/l<=x) f[0][n/l]=vt;
else f[1][l]=vt;
long long t=(n/l)%mod;
g1[vt]=(t*(t+1)%mod*(2*t+1)%mod*ny%mod-1+mod)%mod;
g2[vt]=(t*(t+1)/2%mod-1+mod)%mod;
}
long long w;
for(int i=1;i<=p1;i++){
w=1ll*pr[i]*pr[i];
long long t;
for(int j=1;j<=vt&&w<=v[j];j++){
t=v[j]/pr[i];
if(t<=x) t=f[0][t];
else t=f[1][n/t];
g1[j]=(g1[j]-1ll*pr[i]*pr[i]%mod*(g1[t]-s1[i-1])%mod+mod)%mod;
g2[j]=(g2[j]-1ll*pr[i]*(g2[t]-s2[i-1])%mod+mod)%mod;
}
}
}
long long S(long long p,int q){
if(pr[q]>=p) return 0;
long long td=(p<=x?f[0][p]:f[1][n/p]);
long long re=((g1[td]-s1[q]-g2[td]+s2[q])%mod+mod)%mod;
for(int j=q+1;j<=p1;j++){
if(1ll*pr[j]*pr[j]>p) break;
for(long long o=1,pq=pr[j],t;pq<=p;++o,pq=pq*pr[j]){
t=pq%mod;
re=(re+t*(t-1)%mod*(S(p/pq,j)%mod+(o>1))%mod)%mod;
}
}
return re;
}
int main()
{
scanf("%lld",&n);
init();
printf("%lld",(S(n,0)+1)%mod);
return 0;
}