链接
https://www.luogu.org/problemnew/show/P5325
学习资料
L
o
l
i
c
o
n
A
u
t
o
m
a
t
o
n
LoliconAutomaton
LoliconAutomaton出了一道
m
i
n
_
25
min\_25
min_25筛题,于是我就开始学
m
i
n
_
25
min\_25
min_25筛
推荐学习资料:
https://www.cnblogs.com/zhoushuyu/p/9187319.html
https://www.cnblogs.com/cjyyb/p/9185093.html
两篇一起看食用效果更佳
Min_25筛
日本人的脑洞永远是我们无法想象的大
这算法完全不懂是怎么想到的,只知道太强了,%就完事了
适用条件
对于一个积性函数 f ( x ) f(x) f(x),当 x ∈ p r i m e x\in prime x∈prime时 f ( x ) f(x) f(x)的表达式是多项式,而且 f ( p c ) f(p^c) f(pc)可以快速计算,那么这玩意就可以用 m i n _ 25 min\_25 min_25筛来做
第一步,求自变量为质数的 f ( x ) f(x) f(x)之和
现在对于这个多项式函数
f
(
x
)
f(x)
f(x),我把它每一项拆出来单独做(最后再加起来),设
F
(
x
)
=
x
k
F(x)=x^k
F(x)=xk,现在我们要对所有满足
x
∈
p
r
i
m
e
x\in prime
x∈prime的
x
x
x求
F
(
x
)
F(x)
F(x)的和
注意,这里的
F
(
x
)
=
x
k
F(x)=x^k
F(x)=xk是对所有的
x
x
x成立的,也就是说这个函数并不是原来的那个积性函数了,但是在质数处的取值是和原来的函数一样的,既然现在只求质数处的函数值之和,那么也就不会影响最后结果的正确性
设
P
j
P_j
Pj为第
j
j
j大的质数(
P
1
=
2
,
P
2
=
3
,
P
3
=
5...
P_1=2,P_2=3,P_3=5...
P1=2,P2=3,P3=5...)
设
g
(
n
,
j
)
g(n,j)
g(n,j)为把最小质因子
<
=
P
j
<=P_j
<=Pj的那些合数去掉之后,剩下的数的
F
(
x
)
F(x)
F(x)之和
严谨的写一下:
g
(
n
,
j
)
=
∑
x
=
2
n
F
(
x
)
[
(
x
∈
p
i
r
m
e
)
∨
(
m
i
n
p
(
x
)
>
P
j
)
]
g(n,j)=\sum_{x=2}^nF(x)[(x\in pirme)\vee (minp(x)>P_j)]
g(n,j)=x=2∑nF(x)[(x∈pirme)∨(minp(x)>Pj)]
这个东西可以用来递推,最终求出
g
(
n
,
n
u
m
)
g(n,num)
g(n,num)就是答案,其中
n
u
m
num
num表示不大于
n
\sqrt n
n的质数的总数(因为每个合数都会被
≤
n
\leq \sqrt n
≤n的质数筛掉)
为了便于理解,我举个例子,假设
F
(
p
)
=
p
F(p)=p
F(p)=p,那么
f
(
p
)
f(p)
f(p)序列就是
f
(
2
)
=
2
,
f
(
3
)
=
3
,
f
(
4
)
=
4
,
f
(
5
)
=
5
f(2)=2,f(3)=3,f(4)=4,f(5)=5
f(2)=2,f(3)=3,f(4)=4,f(5)=5,那么
g
(
5
,
0
)
=
f
(
2
)
+
f
(
3
)
+
f
(
4
)
+
f
(
5
)
g(5,0)=f(2)+f(3)+f(4)+f(5)
g(5,0)=f(2)+f(3)+f(4)+f(5)
g
(
5
,
1
)
=
f
(
3
)
+
f
(
5
)
g(5,1)=f(3)+f(5)
g(5,1)=f(3)+f(5)
g
(
5
,
2
)
=
f
(
5
)
g(5,2)=f(5)
g(5,2)=f(5)
g
(
5
,
3
)
=
0
g(5,3)=0
g(5,3)=0
可以发现随着
j
j
j的增大,这就是素数筛的过程,不停的用素数把某些项去掉
现在定义完了,来看看这东西怎么算
显然当
j
>
s
q
r
t
(
n
)
j>sqrt(n)
j>sqrt(n)时,
g
(
n
,
j
)
=
g
(
n
,
j
−
1
)
g(n,j)=g(n,j-1)
g(n,j)=g(n,j−1),因为
P
j
P_j
Pj这个素数没有筛掉任何一项
当
j
≤
s
q
r
t
(
n
)
j\leq sqrt(n)
j≤sqrt(n)时,看看它筛掉了哪些?显然是那些最小质因子等于
P
j
P_j
Pj的数字,假设这个数除以
P
j
P_j
Pj之后得到的商为
x
x
x,那么
P
j
≤
x
P_j\leq x
Pj≤x且
x
P
j
≤
n
xP_j\leq n
xPj≤n,可以得到
P
j
≤
x
≤
⌊
n
P
j
⌋
P_j\leq x \leq \lfloor\frac{n}{P_j}\rfloor
Pj≤x≤⌊Pjn⌋,再根据
F
F
F的完全积性,得到
g
(
n
,
j
)
=
g
(
n
,
j
−
1
)
−
F
(
P
j
)
(
g
(
⌊
n
P
j
⌋
,
j
−
1
)
−
∑
i
=
1
j
−
1
F
(
P
i
)
)
g(n,j)=g(n,j-1)-F(P_j)(g(\lfloor\frac{n}{P_j}\rfloor,j-1)-\sum_{i=1}^{j-1}F(P_i))
g(n,j)=g(n,j−1)−F(Pj)(g(⌊Pjn⌋,j−1)−∑i=1j−1F(Pi))
可以发现这里的递推种
g
(
x
,
y
)
g(x,y)
g(x,y)的第一个自变量
x
x
x都是
⌊
n
k
⌋
\lfloor\frac{n}{k}\rfloor
⌊kn⌋的形式,所以很幸运我们的
x
x
x的取值只有根号种
总结一下
g
(
n
,
j
)
=
{
g
(
n
,
j
−
1
)
P
j
>
n
g
(
n
,
j
−
1
)
−
F
(
P
j
)
(
g
(
⌊
n
P
j
⌋
,
j
−
1
)
−
∑
i
=
1
j
−
1
F
(
P
i
)
)
P
j
≤
n
g(n,j)= \begin{cases} g(n,j-1) &&P_j>\sqrt n \\ g( n , j-1 )-F(P_j)(g(\lfloor\frac{n}{P_j}\rfloor,j-1)-\sum_{i=1}^{j-1}F(P_i)) && P_j \leq \sqrt n \\ \end{cases}
g(n,j)={g(n,j−1)g(n,j−1)−F(Pj)(g(⌊Pjn⌋,j−1)−∑i=1j−1F(Pi))Pj>nPj≤n
这玩意具体怎么算呢?
首先我需要一个
F
F
F函数的前缀和
然后观察发现
j
j
j在递推过程中每次减
1
1
1,那既然如此我像背包那样倒过来循环的话,就能少一维空间,一维数组就可以了
大体步骤就是:
1.初始化
g
(
⌊
n
i
⌋
,
0
)
g(\lfloor\frac{n}{i}\rfloor,0)
g(⌊in⌋,0)
2.筛素数并计算
F
F
F的前缀和
3.递推算
g
(
l
f
l
o
o
r
n
i
⌋
,
j
)
g(lfloor\frac{n}{i}\rfloor,j)
g(lfloorin⌋,j)
第二步,算答案
刚才算了质数的,现在算合数的并且合并出答案
设
S
(
n
,
j
)
S(n,j)
S(n,j)表示最小质因子
≥
P
j
\geq P_j
≥Pj且
≤
n
\leq n
≤n的
x
x
x的函数值之和
即
S
(
n
,
j
)
=
∑
i
=
2
n
f
(
i
)
[
m
i
n
p
(
i
)
≥
P
j
]
S(n,j)=\sum_{i=2}^nf(i)[minp(i)\geq P_j]
S(n,j)=i=2∑nf(i)[minp(i)≥Pj]
(注意这里变成
f
f
f了,刚才一直在讨论
F
F
F,不要搞混)
然后分质数、合数分开算
质数的贡献就是
g
(
n
,
n
u
m
)
−
∑
i
=
1
j
−
1
f
(
i
)
g(n,num)-\sum_{i=1}^{j-1}f(i)
g(n,num)−∑i=1j−1f(i),这很容易理解,就是全部的减去太小的
合数的贡献就是
∑
k
=
j
p
k
2
≤
n
∑
e
=
1
p
k
e
+
1
≤
n
S
(
⌊
n
P
k
e
⌋
,
k
+
1
)
f
(
P
k
e
)
+
f
(
p
k
e
+
1
)
\sum_{k=j}^{pk^2\leq n}\sum_{e=1}^{p_k^{e+1}\leq n}S(\lfloor\frac{n}{P_k^e}\rfloor,k+1)f(P_k^e)+f(p_k^{e+1})
∑k=jpk2≤n∑e=1pke+1≤nS(⌊Pken⌋,k+1)f(Pke)+f(pke+1),这部分啥意思呢?既然我只算合数的,那我先枚举最小质因数
P
k
P_k
Pk,并枚举其幂次
e
e
e,剩下的部分可能是大小不超过
⌊
n
P
k
e
⌋
\lfloor\frac{n}{P_k^e}\rfloor
⌊Pken⌋且约数只由大于第
k
k
k个质数的质数组成,也可能包含这个质数的幂(为了不重复计算只需要考虑一次幂),
p
k
e
+
1
≤
n
p_k^{e+1}\leq n
pke+1≤n这个不等式的含义是,表面上先保证
f
(
p
k
e
+
1
)
f(p_k^{e+1})
f(pke+1)合法,其次我可以写出
p
k
e
p
k
+
1
≤
n
p_k^ep_{k+1}\leq n
pkepk+1≤n,当这个不等式不满足的时候,
S
(
⌊
n
P
k
e
⌋
,
k
+
1
)
S(\lfloor\frac{n}{P_k^e}\rfloor,k+1)
S(⌊Pken⌋,k+1)显然为
0
0
0,也就没必要算下去
递推式写出来如下:
S
(
n
,
j
)
=
g
(
n
,
n
u
m
)
−
∑
i
=
1
j
−
1
f
(
i
)
+
∑
k
=
j
p
k
2
≤
n
∑
e
=
1
p
k
e
+
1
≤
n
S
(
⌊
n
P
k
e
⌋
,
k
+
1
)
f
(
P
k
e
)
+
f
(
p
k
e
+
1
)
S(n,j)=g(n,num)-\sum_{i=1}^{j-1}f(i)+\sum_{k=j}^{pk^2\leq n}\sum_{e=1}^{p_k^{e+1}\leq n}S(\lfloor\frac{n}{P_k^e}\rfloor,k+1)f(P_k^e)+f(p_k^{e+1})
S(n,j)=g(n,num)−i=1∑j−1f(i)+k=j∑pk2≤ne=1∑pke+1≤nS(⌊Pken⌋,k+1)f(Pke)+f(pke+1)
这一部分的实现非常直接,就是个递归,连记忆化都没有
时间复杂度
参考:
https://www.luogu.org/blog/user54214/solution-p5325
貌似大家口径不是很统一,说法有两种,要么
O
(
n
1
−
ϵ
)
O(n^{1-\epsilon})
O(n1−ϵ),要么
O
(
n
3
4
l
o
g
n
)
O(\frac{n^{\frac{3}{4}}}{log_n})
O(lognn43)
反正我是不会分析…
代码
#include <bits/stdc++.h>
#define maxn 200010
#define mod 1000000007ll
#define cl(x) memset(x,0,sizeof(x))
using namespace std;
typedef long long ll;
struct EasyMath
{
ll prime[maxn];
bool mark[maxn];
ll fastpow(ll a, ll b, ll c)
{
ll t(a%c), ans(1ll);
for(;b;b>>=1,t=t*t%c)if(b&1)ans=ans*t%c;
return ans;
}
void get_prime(ll N)
{
ll i, j;
for(i=2;i<=N;i++)mark[i]=false;
*prime=0;
for(i=2;i<=N;i++)
{
if(!mark[i])prime[++*prime]=i;
for(j=1;j<=*prime and i*prime[j]<=N;j++)
{
mark[i*prime[j]]=true;
if(i%prime[j]==0)break;
}
}
}
}em;
struct min_25 //f(p^k)=p^k * (p^k - 1)
{
ll n, sf1[maxn], sf2[maxn], g1[maxn], g2[maxn], id1[maxn], id2[maxn], lis[maxn], _m;
vector<ll> index;
inline ll getid(ll x)
{
return x<=_m ? id1[x] : id2[n/x];
}
void calcg()
{
ll i, j;
//初始化g
ll _2=em.fastpow(2,mod-2,mod), _6=em.fastpow(6,mod-2,mod);
for(i=1;i<=n;i=j+1)
{
j=n/(n/i);
lis[++*lis]=n/i;
if(n/i<=_m)id1[n/i]=*lis;
else id2[n/(n/i)]=*lis;
ll t=(n/i)%mod;
g1[*lis] = ( t*(t+1)%mod*_2 -1 )%mod;
g2[*lis] = ( t*(t+1)%mod*(2*t+1)%mod*_6 -1 )%mod;
//g[j]=\sum_{i=2}^j f(j)
}
//求f(p_j)的前缀和
em.get_prime(_m);
for(i=1;i<=*em.prime;i++)
{
auto p=em.prime[i];
sf1[i]=(sf1[i-1]+p)%mod;
sf2[i]=(sf2[i-1]+p*p)%mod;
}
//求g
for(j=1;j<=*em.prime;j++)
{
auto p=em.prime[j];
for(ll k=1;k<=*lis;k++)
{
if(p*p>lis[k])break;
ll t = getid(lis[k]/p);
(g1[k]-=p*(g1[t]-sf1[j-1]))%=mod;
(g2[k]-=p*p%mod*(g2[t]-sf2[j-1]))%=mod;
}
}
}
ll S(ll x, ll y)
{
if(em.prime[y]>x)return 0;
auto t=getid(x);
auto ans=(g2[t]-g1[t])-(sf2[y-1]-sf1[y-1]);
ans%=mod;
for(ll i=y;i<=*em.prime and em.prime[i]*em.prime[i]<=x;i++)
{
for(auto p1=em.prime[i], p2=p1*p1;p2<=x;p1=p2, p2*=em.prime[i])
{
(ans+=S(x/p1,i+1)*(p1*(p1-1)%mod)%mod+p2%mod*(p2%mod-1)%mod)%=mod;
}
}
return ans;
}
ll run(ll N)
{
_m=sqrt(N);
cl(g1), cl(g2);
cl(sf1), cl(sf2);
n=N;
calcg();
return (S(N,1)%mod+1+mod)%mod;
}
}m25;
int main()
{
ll N;
cin>>N;
cout<<m25.run(N);
return 0;
}