Min_25筛学习笔记
一般套路
前提和目的
对一个积性函数 f ( x ) f(x) f(x),求其前缀和函数 S ( n ) = ∑ i = 1 n f ( i ) S(n)=\sum_{i=1}^{n}f(i) S(n)=∑i=1nf(i),并且 f ( p ) f(p) f(p)是一个关于 p p p的多项式, f ( p k ) f(p^k) f(pk)能够快速计算。
实现
令 P i P_i Pi表示第 i i i个质数, P P P是质数集。
定义如下函数:
s
(
n
,
j
)
=
∑
i
=
2
n
f
(
n
)
[
min
p
∣
i
p
>
P
j
]
g
(
n
,
j
)
=
∑
i
=
2
n
f
(
i
)
[
i
∈
P
∨
m
i
n
p
∣
i
p
>
P
j
]
q
(
n
)
=
∑
P
i
≤
n
f
(
P
i
)
s(n,j)=\sum_{i=2}^{n}f(n)[\min_{p|i}p>P_j]\\ g(n,j)=\sum_{i=2}^{n}f(i)[i\in P \lor min_{p|i}p>P_j]\\ q(n)=\sum_{P_i\le n}f(P_i)
s(n,j)=i=2∑nf(n)[p∣iminp>Pj]g(n,j)=i=2∑nf(i)[i∈P∨minp∣ip>Pj]q(n)=Pi≤n∑f(Pi)
函数 s s s
分别计算质数部分和合数部分的值。
质数部分: q ( n ) − ∑ i = 1 j f ( P i ) q(n)-\sum_{i=1}^{j}f(P_i) q(n)−∑i=1jf(Pi)。
合数部分:被算入的合数的最小的质数一定大于 P j P_j Pj,那么不妨枚举是 P k P_k Pk并再枚举是 e e e次方即 P k e P_k^e Pke,那么除掉 P k e P_k^e Pke后剩下的部分的质数一定都是大于 P k P_k Pk的,再根据 f ( x ) f(x) f(x)是积性函数可知这部分的贡献就是 ∑ k > j ∑ e = 1 P k e ≤ n f ( P k e ) ( s ( ⌊ n P k e ⌋ , k ) + [ e > 1 ] ) \sum_{k>j}\sum_{e=1}^{P_k^e\le n}f(P_k^e)(s(\lfloor \frac{n}{P_k^e} \rfloor,k)+[e>1]) ∑k>j∑e=1Pke≤nf(Pke)(s(⌊Pken⌋,k)+[e>1]),这里的 [ e > 1 ] [e>1] [e>1]是因为当 e > 1 e>1 e>1时 f ( P k e ) f(P_k^e) f(Pke)也要算入,但是 e = 1 e=1 e=1是在前面的质数部分就已经算入了,所以不用算入。
综上,
s
(
n
,
j
)
s(n,j)
s(n,j)的转移如下:
s
(
n
,
j
)
=
q
(
n
)
−
∑
i
=
1
j
f
(
P
i
)
+
∑
k
>
j
∑
e
=
1
P
k
e
≤
n
f
(
P
k
e
)
(
s
(
⌊
n
P
k
e
⌋
,
k
)
+
[
e
>
1
]
)
s(n,j)=q(n)-\sum_{i=1}^{j}f(P_i)+\sum_{k>j}\sum_{e=1}^{P_k^e\le n}f(P_k^e)(s(\lfloor \frac{n}{P_k^e} \rfloor,k)+[e>1])
s(n,j)=q(n)−i=1∑jf(Pi)+k>j∑e=1∑Pke≤nf(Pke)(s(⌊Pken⌋,k)+[e>1])
函数g
上面需要求 q ( n ) q(n) q(n),这里用 g g g来解决这个问题。
在实现 g g g之前,我们需要构造一个完全积性函数 h ( x ) h(x) h(x),要求 h ( x ) h(x) h(x)在质数处的取值和 f ( x ) f(x) f(x)相同。由于 g g g函数最后会用到的值只有质数的函数值,所以不妨用 h h h代替 f f f。
计算函数 g g g时,不妨考虑 g ( n , j − 1 ) − g ( n , j ) g(n,j-1)-g(n,j) g(n,j−1)−g(n,j),然后从小到大递推。
g ( n , j − 1 ) − g ( n , j ) g(n,j-1)-g(n,j) g(n,j−1)−g(n,j)实际上就是最小的质因子为 P j P_j Pj且本身不为质数的 x x x的 h ( x ) h(x) h(x)的和。当 P j 2 > n P_j^2>n Pj2>n时 g ( n , j − 1 ) = g ( n , j ) g(n,j-1)=g(n,j) g(n,j−1)=g(n,j)因为上述的那种 x x x不存在;当 P j 2 ≤ n P_j^2\le n Pj2≤n时,根据 h ( x ) h(x) h(x)是完全积性函数的性质,可以知道这个就是 h ( P j ) ( g ( ⌊ n P j ⌋ , j − 1 ) − ∑ i = 1 j − 1 h ( P i ) ) h(P_j)(g(\lfloor\frac{n}{P_j}\rfloor,j-1)-\sum_{i=1}^{j-1}h(P_i)) h(Pj)(g(⌊Pjn⌋,j−1)−∑i=1j−1h(Pi))。
综上,
g
(
n
,
j
)
g(n,j)
g(n,j)的转移如下:
g
(
n
,
j
)
=
{
g
(
n
,
j
−
1
)
P
j
2
>
n
g
(
n
,
j
−
1
)
−
h
(
P
j
)
(
g
(
⌊
n
P
j
⌋
,
j
−
1
)
−
∑
i
=
1
j
−
1
h
(
P
i
)
)
P
j
2
≤
n
g(n,j)= \begin{cases} g(n,j-1) & P_j^2 >n\\ g(n,j-1)-h(P_j)(g(\lfloor\frac{n}{P_j}\rfloor,j-1)-\sum_{i=1}^{j-1}h(P_i)) & P_j^2\le n \end{cases}
g(n,j)={g(n,j−1)g(n,j−1)−h(Pj)(g(⌊Pjn⌋,j−1)−∑i=1j−1h(Pi))Pj2>nPj2≤n
初值就是
g
(
n
,
0
)
=
∑
i
=
2
n
h
(
i
)
g(n,0)=\sum_{i=2}^{n}h(i)
g(n,0)=∑i=2nh(i)。
时间复杂度
其实可以发现质数只要筛到 n \sqrt{n} n的级别就可以了,剩下的可以参考数论分块,计算比较繁琐,最后的结论就是 O ( n 3 4 log n ) O(\frac{n^{\frac{3}{4}}}{\log n}) O(lognn43)。
实现技巧
存函数值其实不用 map \text{map} map,对于 x ≤ n x\le \sqrt{n} x≤n可以直接存,对于 x > n x>\sqrt{n} x>n可以按 n x \frac{n}{x} xn来存。
例题
【模板】Min_25筛
拆开来, f ( p k ) = ( p k ) 2 − p k f(p^k)=(p^k)^2-p^k f(pk)=(pk)2−pk,前面的用 h 0 ( x ) = x 2 h_0(x)=x^2 h0(x)=x2,后面的用 h 1 ( x ) = x h_1(x)=x h1(x)=x。
确实模板。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=2e5+5,mod=1e9+7,inv6=166666668,inv2=500000004;
int P[maxn],bz[maxn],tot=0;
int h0[maxn],h1[maxn];
ll num[maxn],cnt=0;
ll n,sq;
struct Array{
int num1[maxn],num2[maxn];
int& operator [] (ll x){
if (x<=sq) return num1[x];
return num2[n/x];
}
}g0,g1;
int S(ll n,int j){
if (P[j]>n) return 0;
int res=1ll*((1ll*g0[n]-1ll*h0[j]+1ll*mod)%mod-(1ll*g1[n]-1ll*h1[j]+1ll*mod)%mod+1ll*mod)%mod;
for (int k=j+1;k<=tot && 1ll*P[k]*P[k]<=n;++k){
ll tmp=P[k];
for (int e=1;tmp<=n;++e,tmp*=P[k]){
int ts=(S(n/tmp,k)+(e>1))%mod;
int tf=1ll*tmp%mod*(tmp%mod-1)%mod;
res=(1ll*res+1ll*tf*ts%mod)%mod;
}
}
return res;
}
int main(){
scanf("%lld",&n);sq=sqrt(n);
for (int i=2;i<=sq;++i){
if (!bz[i])
P[++tot]=i,
h0[tot]=(1ll*h0[tot-1]+1ll*i*i%mod)%mod,
h1[tot]=(1ll*h1[tot-1]+1ll*i)%mod;
for (int j=1;j<=tot && 1ll*i*P[j]<=sq;++j){
bz[i*P[j]]=1;
if (i%P[j]==0) break;
}
}
for (ll tmp=1;tmp<=n;tmp=n/(n/tmp)+1) num[++cnt]=n/tmp;
reverse(num+1,num+1+cnt);
for (int i=1;i<=cnt;++i){
int t=num[i]%mod;
g0[num[i]]=1ll*(t+1)%mod*(2ll*t%mod+1)%mod*t%mod*inv6%mod-1;
g1[num[i]]=1ll*(t+1)*t%mod*inv2%mod-1;
}
for (int j=1;j<=tot;++j){
for (int i=cnt;i>=1;--i)
if (1ll*P[j]*P[j]<=num[i])
g0[num[i]]=(1ll*g0[num[i]]-(1ll*g0[num[i]/P[j]]-1ll*h0[j-1]+1ll*mod)%mod*P[j]%mod*P[j]%mod+1ll*mod)%mod,
g1[num[i]]=(1ll*g1[num[i]]-(1ll*g1[num[i]/P[j]]-1ll*h1[j-1]+1ll*mod)%mod*P[j]%mod+1ll*mod)%mod;
else break;
}
int ans=(S(n,0)+1)%mod;
printf("%d\n",ans);
}