%%%gmh差不多一年前学会min_25筛
%%%某初一大佬似乎已经会了min_25筛
菜哭了
约定
以下记
P
P
P为素数集合,
P
(
n
)
P(n)
P(n)为所有小于等于
n
n
n的素数的集合。
m
i
n
p
(
x
)
minp(x)
minp(x)表示
x
x
x的最小质因子
问题
这种什么筛之类的,多是求积性函数的前缀和的算法。
min_25筛能做的积性函数要满足这两个条件:
- 对于所有 p ∈ P p\in P p∈P, f ( p ) f(p) f(p)能够用多项式表示出来。
- 对于所有 p ∈ P , k ∈ N p \in P,k\in N p∈P,k∈N, f ( p k ) f(p^k) f(pk), f ( p k ) f(p^k) f(pk)能够快速计算。
介绍
min_25筛具体分为两个部分:求素数的答案,和求所有数的答案(不包括 1 1 1的答案, 1 1 1的答案最后自己加上)。
算 g g g
先说素数部分怎么求。
分别枚举每一项的系数,不同的系数分开来算。
以下记素数
x
x
x的函数为
f
(
x
)
f(x)
f(x)(指某一特定的系数下)
设
g
(
n
,
j
)
=
∑
i
∈
P
(
n
)
或
m
i
n
p
(
i
)
>
p
j
f
(
i
)
g(n,j)=\sum_{i \in P(n) 或 minp(i)>p_j}f(i)
g(n,j)=∑i∈P(n)或minp(i)>pjf(i)
考虑从
g
(
n
,
j
−
1
)
g(n,j-1)
g(n,j−1)得到
g
(
n
,
j
)
g(n,j)
g(n,j)
想想
g
(
n
,
j
−
1
)
g(n,j-1)
g(n,j−1)得到
g
(
n
,
j
)
g(n,j)
g(n,j)要去掉些什么,根据概念可知要去掉所有满足
m
i
n
p
(
x
)
=
p
j
minp(x)=p_j
minp(x)=pj的合数
x
x
x。
这个合数最小是 p j 2 p_j^2 pj2,于是当 n < p j 2 n<p_j^2 n<pj2时,有 g ( n , j ) = g ( n , j − 1 ) g(n,j)=g(n,j-1) g(n,j)=g(n,j−1)
古爷:这就是min_25筛的精髓!
有了这个东西,意味着大于 n \sqrt n n的质数都没必要算,
j j j最多取到 ∣ P ( n ) ∣ |P(\sqrt n)| ∣P(n)∣就可以了。
当
n
≥
p
j
2
n\ge p_j^2
n≥pj2时,考虑将
p
j
p_j
pj的倍数筛去。
由于是积性函数,所以可以将
f
(
p
j
)
f(p_j)
f(pj)提出来,剩下的就是
g
(
⌊
n
p
j
⌋
,
j
−
1
)
g(\lfloor\frac{n}{p_j} \rfloor,j-1)
g(⌊pjn⌋,j−1)。
真的是吗?其实这样还算多了小于
p
j
p_j
pj的所有质数的贡献,即
g
(
p
j
−
1
,
j
−
1
)
g(p_j-1,j-1)
g(pj−1,j−1),减去即可。
于是这个时候
g
(
n
,
j
)
=
g
(
n
,
j
−
1
)
+
f
(
p
j
)
(
g
(
⌊
n
p
j
⌋
,
j
−
1
)
−
g
(
p
j
−
1
,
j
−
1
)
)
g(n,j)=g(n,j-1)+f(p_j)(g(\lfloor\frac{n}{p_j} \rfloor,j-1)-g(p_j-1,j-1))
g(n,j)=g(n,j−1)+f(pj)(g(⌊pjn⌋,j−1)−g(pj−1,j−1))
注意一点,上面说“由于是积性函数”,这里应该是广义的积性函数(即便不是题目想要求的),也就是 f ( x ) f(x) f(x),形式为 f ( x ) = x k f(x)=x^k f(x)=xk
这个步骤下我们最终只是需要素数的贡献和,至于合数的贡献,我们把它当做广义积性函数来算。
合数的贡献只是中间值,计算到最后是会消掉的。
临界:当 j = 0 j=0 j=0的时候,直接用自然数幂和来计算。注意减去 1 1 1的贡献。
显然, g ( n , ∣ P ( n ) ∣ ) g(n,|P(n)|) g(n,∣P(n)∣)就是所有小于等于 n n n的质数的答案。
联想?
形象化地理解这个东西:
当从
P
(
n
,
j
−
1
)
P(n,j-1)
P(n,j−1)转移到
P
(
n
,
j
)
P(n,j)
P(n,j)的时候,将
m
i
n
p
(
x
)
=
p
j
minp(x)=p_j
minp(x)=pj的合数
x
x
x去掉。
这个东西看起来很像埃氏筛法。
求 S S S
接下来是计算所有数的答案。
这里的
F
(
x
)
F(x)
F(x)直接表示
x
x
x的贡献(不需要分具体是哪一位)。
设
S
(
n
,
j
)
=
∑
m
i
n
p
(
i
)
≥
p
j
f
(
i
)
S(n,j)=\sum_{minp(i)\ge p_j} f(i)
S(n,j)=∑minp(i)≥pjf(i)
先列出式子:
S
(
n
,
j
)
=
g
(
n
,
∣
P
(
n
)
∣
)
−
∑
k
<
j
F
(
k
)
+
∑
k
≥
j
,
p
k
2
≤
n
∑
e
>
0
,
p
k
e
+
1
≤
n
(
F
(
p
k
e
)
S
(
⌊
n
p
k
e
⌋
,
k
+
1
)
+
F
(
p
k
e
+
1
)
)
S(n,j)=g(n,|P(n)|)-\sum_{k<j} F(k)+\sum_{k\ge j,p_k^2\le n}\sum_{e>0,p_k^{e+1}\le n}(F(p_k^e)S(\lfloor \frac{n}{p_k^e}\rfloor,k+1)+F(p_k^{e+1}))
S(n,j)=g(n,∣P(n)∣)−∑k<jF(k)+∑k≥j,pk2≤n∑e>0,pke+1≤n(F(pke)S(⌊pken⌋,k+1)+F(pke+1))
前面的
g
(
n
,
∣
P
(
n
)
∣
)
−
∑
k
<
j
f
(
k
)
g(n,|P(n)|)-\sum_{k<j} f(k)
g(n,∣P(n)∣)−∑k<jf(k)是所有大于等于
p
j
p_j
pj的素数的答案。
后面就是枚举最小质因子
p
k
p_k
pk及其系数
e
e
e,提出来之后计算。
S
(
⌊
n
p
k
e
⌋
,
k
+
1
)
S(\lfloor \frac{n}{p_k^e}\rfloor,k+1)
S(⌊pken⌋,k+1)如果不为
0
0
0,则至少要满足
p
k
≤
⌊
n
p
k
e
⌋
p_k\le\lfloor\frac{n}{p_k^e}\rfloor
pk≤⌊pken⌋,移项一下就得到了
p
k
e
+
1
≤
n
p_k^{e+1}\le n
pke+1≤n
最后面之所以要加上那个东西,是因为
S
(
⌊
n
p
k
e
⌋
,
k
+
1
)
S(\lfloor \frac{n}{p_k^e}\rfloor,k+1)
S(⌊pken⌋,k+1)中并不包括
p
k
p_k
pk,所以要单独计算
p
k
e
+
1
p_k^{e+1}
pke+1的贡献。
注意,这里的积性函数是“真”的积性函数
对于计算到的每个数,每个质因子都只会枚举一遍。
并不一定是广义积性函数。
答案就是 S ( n , 1 ) + 1 S(n,1)+1 S(n,1)+1,后面那个是 1 1 1的贡献。
自己瞎玩出来的求 S S S做法
观察前面的那个式子,感觉好丑啊。
为什么还要枚举
k
k
k?直接枚举
p
j
p_j
pj的系数就好了啊!
S
(
n
,
j
)
=
S
(
n
,
j
+
1
)
+
F
(
p
j
)
+
∑
e
>
0
,
p
j
e
+
1
≤
n
(
F
(
p
j
e
)
S
(
⌊
n
p
j
e
⌋
)
+
F
(
p
j
e
+
1
)
)
S(n,j)=S(n,j+1)+F(p_j)+\sum_{e>0,p_j^{e+1}\le n} (F(p_j^e)S(\lfloor\frac{n}{p_j^e}\rfloor)+F(p_j^{e+1}))
S(n,j)=S(n,j+1)+F(pj)+∑e>0,pje+1≤n(F(pje)S(⌊pjen⌋)+F(pje+1))
哇哈哈看起来似乎连
g
g
g都不用求
然后得到了古爷的指导:这个东西,
O
(
n
)
O(n)
O(n)起步。
后面的那一段求和只有
p
j
2
≤
n
p_j^2\le n
pj2≤n的时候是要做的,但是前面的那个要做
∣
P
(
n
)
∣
|P(n)|
∣P(n)∣遍。
于是就有个简单的优化:
当
p
j
2
>
n
p_j^2>n
pj2>n的时候,直接计算
∑
p
k
2
>
n
F
(
p
k
)
\sum_{p_k^2>n} F(p_k)
∑pk2>nF(pk),加上之后退出。
这个东西用
g
g
g来求。所以还是要求
g
g
g啊……
另一种高级的求 S S S做法
待填坑。
实现?
观察一下式子,可以发现,要计算到的
n
n
n都满足
n
∈
{
⌊
N
i
⌋
∣
i
∈
[
1
,
N
]
,
i
∈
Z
}
n\in\{\lfloor\frac{N}{i}\rfloor|i\in[1,N],i \in Z\}
n∈{⌊iN⌋∣i∈[1,N],i∈Z}
N
N
N是题目给出的
N
N
N。
自己证
可以发现这个集合的大小不超过
2
N
2\sqrt N
2N
考虑预处理出每个
g
(
n
,
∣
P
(
n
)
∣
)
g(n,|P(n)|)
g(n,∣P(n)∣)。
简写为
g
n
g_n
gn
先考虑怎么存储。对于每个
n
n
n,可以按小于等于或者大于
N
\sqrt N
N讨论,后者借助
⌊
N
n
⌋
\lfloor\frac{N}{n}\rfloor
⌊nN⌋来存储。可以证明
⌊
N
n
⌋
\lfloor\frac{N}{n}\rfloor
⌊nN⌋对于不同的
n
n
n不重复。
接下来考虑模拟筛法来求:
枚举质数
p
j
p_j
pj,然后计算当前这一层的
g
n
g_n
gn
具体见代码
求
S
S
S的时候,不用记忆化,不用记忆化,不用记忆化!
另外,用前面那个方法就可以了,中间那个方法听说会慢,我自己脑补出来的那个方法和前面那个差不多(无法理解!为什么看起来优美那么多却没有地变快?)
实现
例题见洛谷板题。
大众做法
using namespace std;
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
#include <cassert>
#define M 100010
#define mo 1000000007
#define ll long long
ll qpow(ll x,ll y=mo-2){
ll r=1;
for (;y;y>>=1,x=x*x%mo)
if (y&1)
r=r*x%mo;
return r;
}
ll _n,sq;
int p[M],np,sf[M],sg1[M],sg2[M];
bool inp[M];
int lsp[M];
#define f(p) ((ll)(p)*((p)-1))
ll w[M*2],cnt;
int id1[M],id2[M];
#define id(d) (d<=sq?id1[d]:id2[_n/d])
ll g1[M*2],g2[M*2];
void init(ll n){
ll sq=sqrt(n);
ll inv2=qpow(2),inv6=qpow(6);
for (ll i=1;i<=n;++i){
ll j=n/i;
w[++cnt]=j;
(j<=sq?id1[j]:id2[n/j])=cnt;
i=n/j;
j%=mo;
g1[cnt]=(j*(j+1)%mo*inv2-1+mo)%mo;
g2[cnt]=(j*(j+1)%mo*(2*j+1)%mo*inv6-1+mo)%mo;
}
for (int j=1;j<=np;++j)
for (int i=1;i<=cnt && (ll)p[j]*p[j]<=w[i];++i){
ll d=w[i]/p[j],k=id(d);
g1[i]=((g1[i]-(ll)p[j]*(g1[k]-sg1[j-1]))%mo+mo)%mo;
g2[i]=((g2[i]-(ll)p[j]*p[j]%mo*(g2[k]-sg2[j-1]))%mo+mo)%mo;
}
}
ll S(ll n,int j){
ll tmp=id(n),r=(g2[tmp]-g1[tmp])-(j?sf[j-1]:0);
r%=mo,r+=r<0?mo:0;
if (j>sq)
return r;
for (int k=j;k<=np && (ll)p[k]*p[k]<=n;++k)
for (ll w=p[k];w*p[k]<=n;w*=p[k])
(r+=f(w%mo)%mo*S(n/w,k+1)+f(w*p[k]%mo))%=mo;
return r;
}
int main(){
scanf("%lld",&_n);
sq=sqrt(_n);
for (int i=2;i<=sq;++i){
if (!inp[i]){
p[++np]=i;
sf[np]=(sf[np-1]+f(i))%mo;
sg1[np]=(sg1[np-1]+i)%mo;
sg2[np]=(sg2[np-1]+(ll)i*i)%mo;
}
lsp[i]=np;
for (int j=1;j<=np && (ll)i*p[j]<=sq;++j){
inp[i*p[j]]=1;
if (i%p[j]==0)
break;
}
}
init(_n);
printf("%lld\n",(1+S(_n,1))%mo);
return 0;
}
我的辣鸡做法
using namespace std;
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
#include <cassert>
#define M 100010
#define mo 1000000007
#define ll long long
ll qpow(ll x,int y=mo-2){
ll r=1;
for (;y;y>>=1,x=x*x%mo)
if (y&1)
r=r*x%mo;
return r;
}
ll _n,sq;
int p[M],np,sf[M],sg1[M],sg2[M];
bool inp[M];
int lsp[M];
#define f(p) ((ll)(p)*((p)-1))
ll w[M*2],cnt;
int id1[M],id2[M];
#define id(d) (d<=sq?id1[d]:id2[_n/d])
ll g1[M*2],g2[M*2];
void init(ll n){
ll sq=sqrt(n);
ll inv2=qpow(2),inv6=qpow(6);
for (ll i=1;i<=n;++i){
ll j=n/i;
w[++cnt]=j;
(j<=sq?id1[j]:id2[n/j])=cnt;
i=n/j;
j%=mo;
g1[cnt]=(j*(j+1)%mo*inv2-1+mo)%mo;
g2[cnt]=(j*(j+1)%mo*(2*j+1)%mo*inv6-1+mo)%mo;
}
for (int j=1;j<=np;++j)
for (int i=1;i<=cnt && (ll)p[j]*p[j]<=w[i];++i){
ll d=w[i]/p[j],k=id(d);
g1[i]=((g1[i]-(ll)p[j]*(g1[k]-sg1[j-1]))%mo+mo)%mo;
g2[i]=((g2[i]-(ll)p[j]*p[j]%mo*(g2[k]-sg2[j-1]))%mo+mo)%mo;
}
}
ll S(ll n,int j){
ll r=0;
if (j>np || (ll)p[j]*p[j]>n){
int l=min(np,j-1),t=id(n);
r+=g2[t]-g1[t]-sf[l];
return (r%mo+mo)%mo;
}
r=(f(p[j]%mo)+S(n,j+1))%mo;
ll w=p[j];
for (int e=1;w*p[j]<=n;++e,w*=p[j])
(r+=f(w%mo)%mo*S(n/w,j+1)+f(w*p[j]%mo))%=mo;
return r;
}
int main(){
freopen("in.txt","r",stdin);
scanf("%lld",&_n);
sq=sqrt(_n);
for (int i=2;i<=sq;++i){
if (!inp[i]){
p[++np]=i;
sf[np]=(sf[np-1]+f(i))%mo;
sg1[np]=(sg1[np-1]+i)%mo;
sg2[np]=(sg2[np-1]+(ll)i*i)%mo;
}
lsp[i]=np;
for (int j=1;j<=np && (ll)i*p[j]<=sq;++j){
inp[i*p[j]]=1;
if (i%p[j]==0)
break;
}
}
init(_n);
printf("%lld\n",(1+S(_n,1))%mo);
return 0;
}