笔记写在前面:
m
i
n
25
min25
min25 筛是用来求积性函数前缀和
∑
i
=
1
n
f
(
i
)
\sum_{i=1}^nf(i)
∑i=1nf(i) 的一个筛法,其时间复杂度为
O
(
n
3
4
log
n
)
O(\frac{n^{\frac 34}}{\log n})
O(lognn43) 大约
1
s
1s
1s 能跑
1
e
10
1e10
1e10。
该积性函数需要满足:
- f ( p ) f(p) f(p) 是一个关于质数 p p p 的项数较小的多项式 或者 能快速求值
- f ( p k ) f(p^k) f(pk) 可以快速求值
我们把
f
(
i
)
f(i)
f(i) 按
i
i
i 是否是质数分为两组,分别计算:
对于质数部分:
我们定义一个完全积性函数
F
(
i
)
F(i)
F(i) (我们不需要关注其具体形式),该函数满足
F
(
p
)
=
f
(
p
)
F(p)=f(p)
F(p)=f(p) (其中
p
p
p 是一个质数)。
定义一个神仙
d
p
dp
dp :
g
(
n
,
j
)
=
∑
i
=
1
n
F
(
i
)
[
i
是
质
数
或
i
的
最
小
质
因
子
大
于
第
j
个
质
数
]
g(n,j)=\sum_{i=1}^nF(i)[i是质数\ 或\ i的最小质因子大于第j个质数]
g(n,j)=i=1∑nF(i)[i是质数 或 i的最小质因子大于第j个质数]
注意:在
j
j
j 趋于无穷,即
g
(
n
,
=
+
∞
)
g(n,=+\infty)
g(n,=+∞) 时,有
∑
i
=
1
n
F
(
i
)
=
∑
i
=
1
n
f
(
i
)
\sum_{i=1}^nF(i)=\sum_{i=1}^nf(i)
∑i=1nF(i)=∑i=1nf(i)
接下来考虑
g
(
n
,
j
)
g(n,j)
g(n,j) 的转移方程
设第
j
j
j 个质数为
p
j
p_j
pj :
当
p
j
>
n
p_j>\sqrt n
pj>n ,即(
p
j
2
>
n
p^2_j>n
pj2>n)时:
我们发现若
p
j
p_j
pj 是一个质数的最小质因子且这个数要被
g
(
n
,
j
)
g(n,j)
g(n,j) 计算,他至少是
p
j
2
p_j^2
pj2 ,而
p
j
2
>
n
p^2_j > n
pj2>n 所以此时有
g
(
n
,
j
)
=
g
(
n
,
j
−
1
)
g(n,j)=g(n,j-1)
g(n,j)=g(n,j−1)
当
p
j
≤
n
p_j\le \sqrt n
pj≤n ,即(
p
j
2
≤
n
p^2_j\le n
pj2≤n)时:
不难发现,从
g
(
n
,
j
−
1
)
g(n,j-1)
g(n,j−1) 转移到
g
(
n
,
j
)
g(n,j)
g(n,j) 时值是必然减小的,其减小的值就是最小质因子为
p
j
p_j
pj 的合数的值(因为
g
(
n
,
j
−
1
)
g(n,j-1)
g(n,j−1) 统计的所有数的最小质因子
m
i
n
p
>
j
−
1
min_p > j-1
minp>j−1 ,
g
(
n
,
j
)
g(n,j)
g(n,j) 统计的数字的最小质因子
m
i
n
p
>
j
min_p>j
minp>j ,所以被减去的就是最小质因子为
p
j
p_j
pj 的合数。注意
p
j
p_j
pj 是质数一定会被计算,所以需要被保留下来)
我们不妨设这些合数为
x
x
x ,他们的函数值为
F
(
x
)
F(x)
F(x) ,由
F
(
x
)
F(x)
F(x) 是完全积性函数可得
F
(
x
)
=
F
(
p
j
)
⋅
F
(
x
p
j
)
F(x)=F(p_j)·F(\frac x{p_j})
F(x)=F(pj)⋅F(pjx)
其中
F
(
x
p
j
)
F(\frac x{p_j})
F(pjx) 是可以用
g
(
⌊
n
p
j
⌋
,
j
−
1
)
−
∑
i
=
1
j
−
1
F
(
p
j
)
g(\lfloor \frac{n}{p_j} \rfloor,j-1)-\sum_{i=1}^{j-1}F(p_j)
g(⌊pjn⌋,j−1)−∑i=1j−1F(pj) 表示的(这里如果不明白,可以自己手写几个数字模拟一下)
综上我们就有了
g
(
n
,
j
)
g(n,j)
g(n,j) 的转移方程:
g
(
n
,
j
)
=
{
g
(
n
,
j
−
1
)
p
j
2
>
n
g
(
n
,
j
−
1
)
−
F
(
p
j
)
⋅
(
g
(
⌊
n
p
j
⌋
,
j
−
1
)
−
∑
i
=
1
j
−
1
F
(
p
j
)
)
p
j
2
≤
n
g(n,j)= \begin{cases} g(n,j-1) & {p_j^2>n}\\ g(n,j-1)-F(p_j)·(g(\lfloor \frac{n}{p_j} \rfloor,j-1)-\sum_{i=1}^{j-1}F(p_j)) & {p_j^2\le n}\\ \end{cases}
g(n,j)={g(n,j−1)g(n,j−1)−F(pj)⋅(g(⌊pjn⌋,j−1)−∑i=1j−1F(pj))pj2>npj2≤n
现在我们来加上合部分来计算整体答案:
我们引入一个新的没那么神仙
d
p
dp
dp 状态:
S
(
n
,
j
)
=
∑
i
=
1
n
f
(
i
)
[
i
的
最
小
质
因
子
大
于
第
j
个
质
数
]
S(n,j)=\sum_{i=1}^nf(i)[i的最小质因子大于第j个质数]
S(n,j)=i=1∑nf(i)[i的最小质因子大于第j个质数]
我们最终的答案就是
S
(
n
,
0
)
+
1
S(n,0)+1
S(n,0)+1 (注意
f
(
i
)
f(i)
f(i) 是一个积性函数)
接下来考虑如何计算
S
(
n
,
j
)
S(n,j)
S(n,j),其可以利用
g
(
n
,
j
)
g(n,j)
g(n,j) 来得到一个方程,如下:
S
(
n
,
j
)
=
g
(
n
,
∣
P
∣
)
−
∑
i
=
1
j
−
1
f
(
p
i
)
+
∑
k
=
j
+
1
p
k
2
≤
n
∑
e
=
1
p
k
e
≤
n
f
(
p
k
e
)
(
S
(
⌊
n
p
k
e
⌋
,
k
)
+
[
e
>
1
]
)
S(n,j)=g(n,|P|)-\sum_{i=1}^{j-1}f(p_i)+\sum_{k=j+1}^{p_k^2\le n}\sum_{e=1}^{p^e_k\le n}f(p_k^e)(S(\lfloor \frac{n}{p_k^e} \rfloor,k)+[e>1])
S(n,j)=g(n,∣P∣)−i=1∑j−1f(pi)+k=j+1∑pk2≤ne=1∑pke≤nf(pke)(S(⌊pken⌋,k)+[e>1])
其中
∣
P
∣
|P|
∣P∣ 表示
n
\sqrt n
n 以内的质数个数(这里我们用到的
f
f
f 和
S
S
S 拆开是积性函数的性质)
这个递归方程的时间复杂度被证明是
O
(
n
3
4
log
n
)
O(\frac{n^{\frac 34}}{\log n})
O(lognn43)
g
(
n
,
∣
P
∣
)
−
∑
i
=
1
j
−
1
f
(
p
i
)
g(n,|P|)-\sum_{i=1}^{j-1}f(p_i)
g(n,∣P∣)−∑i=1j−1f(pi) 就是质数的贡献:
g
(
n
,
∣
P
∣
)
g(n,|P|)
g(n,∣P∣) 只保留了
n
n
n 以内的质数和,因为
n
n
n 以内没有最小质因子大于
n
\sqrt n
n 的数字
而
S
(
n
,
∣
p
∣
)
S(n,|p|)
S(n,∣p∣) 求的是最小质因子大于第
j
j
j 个质数的数的和,所以需要减去前
j
−
1
j-1
j−1 个质数对应函数值的前缀和
接下来考虑合数的贡献:
因为
S
(
n
,
j
)
S(n,j)
S(n,j) 是最小质因子大于第
j
j
j 个质数的函数值的前缀和,所以我们从第
j
+
1
j+1
j+1 个质数开始枚举一个合数的最小质因子
因为一个数可能是同一个质数的多次幂,所以需要枚举质数的幂次,依次
p
k
2
,
p
k
3
,
.
.
.
,
p
k
e
p_k^2,p_k^3,...,p_k^e
pk2,pk3,...,pke 直到
p
k
e
≤
n
p_k^e\le n
pke≤n
有可能一个数只是这个质数的次方,他不会被
S
S
S 计算,而且
e
e
e 应该大于
1
1
1 ,否则他会算上这个质数
这样的质数枚举到
p
k
2
≤
n
p_k^2 \le n
pk2≤n 即可
一些技巧与启发
- 对于质数部分, m i n 25 min25 min25 筛每次将合数的最小质因子筛去. 我们可以利用这个性质处理一些有关最小质因子的问题
- 对于合数部分,我们每次从小到大枚举质因子,我们可以对于枚举的质因子加上一些约束(注意同时修改质数部分),也可以对最大质因子,次大质因子进行计算(UOJ188)
- 筛数时可以考虑每个数的最小质因子,因为合数的最小质因子是 n \sqrt n n 的
题目链接:点击这里
题目大意:
定义积性函数
f
(
x
)
f(x)
f(x) ,且
f
(
p
k
)
=
p
k
(
p
k
−
1
)
f(p^k)=p^k(p^k-1)
f(pk)=pk(pk−1) (其中
p
p
p 是一个质数),现给出一个
n
n
n 求:
∑
i
=
1
n
f
(
i
)
m
o
d
1
e
9
+
7
\sum_{i=1}^nf(i) \mod 1e9+7
i=1∑nf(i)mod1e9+7
题目分析:
根据上述两个递推公式我们需要预处理:
- ∑ i = 1 j − 1 F ( p i ) \sum_{i=1}^{j-1} F(p_i) ∑i=1j−1F(pi) , F F F 函数的质数前缀和,注意我们无需构造 F F F ,用 f f f 代替一下即可
- n n n 的整除分块的所有可能
- g ( n , 0 ) g(n,0) g(n,0) 递推起点
对于预处理
1
1
1 :
我们发现
f
(
p
1
)
=
p
1
(
p
1
−
1
)
=
p
2
−
p
f(p^1)=p^1(p^1-1)=p^{2}-p
f(p1)=p1(p1−1)=p2−p ,所以我们把
p
p
p 和
p
2
p^2
p2 分别求前缀和
s
p
1
[
]
,
s
p
2
[
]
sp1[],sp2[]
sp1[],sp2[]
而且,由于我们取的
+
∞
+\infty
+∞ 是
n
\sqrt n
n ,所以我们可以在线筛时顺便求出
s
p
1
[
]
,
s
p
2
[
]
sp1[],sp2[]
sp1[],sp2[]
对于预处理
2
2
2 :
使用整除分块时,对于一个数
n
n
n ,它的整除分块的值约有
n
\sqrt n
n 个,我们用
w
[
]
w[]
w[] 保存这些值,
t
o
t
tot
tot 范围是
[
1
,
t
o
t
]
[1,tot]
[1,tot] ,同时用
i
d
1
[
]
,
i
d
2
[
]
id1[],id2[]
id1[],id2[] 两个数组保存它们的下标
i
d
1
[
]
id1[]
id1[] 用来存
n
l
≤
n
\frac nl \le n
ln≤n 时的下标,
i
d
2
[
]
id2[]
id2[] 用来存
n
l
>
n
\frac nl > n
ln>n 的下标,由于
n
l
\frac nl
ln 可能很大,所以反过来用
n
n
l
\frac n {\frac nl}
lnn 来存
n
l
\frac nl
ln 的下标,这样空间复杂度就在
n
\sqrt n
n 左右了
对于预处理
3
3
3 :
在预处理整除分块时我们可以顺便求出
g
1
(
n
,
0
)
,
g
2
(
n
,
0
)
g_1(n,0),g_2(n,0)
g1(n,0),g2(n,0)
由于
f
1
(
x
)
=
x
,
f
2
(
x
)
=
x
2
f_1(x) = x,f_2(x)=x^2
f1(x)=x,f2(x)=x2 ,所以这里可以用求和公式
∑
i
=
1
n
=
n
(
n
+
1
)
2
,
∑
i
=
1
n
i
2
=
n
(
n
+
1
)
(
2
n
+
1
)
6
\sum_{i=1}^n=\frac{n(n+1)}2,\sum_{i=1}^ni^2=\frac{n(n+1)(2n+1)}6
∑i=1n=2n(n+1),∑i=1ni2=6n(n+1)(2n+1) 直接求出
g
1
(
n
,
0
)
,
g
2
(
n
,
0
)
g_1(n,0),g_2(n,0)
g1(n,0),g2(n,0) ,注意减
1
1
1 是因为
1
1
1 不符合条件
然后就可以从
0
0
0 递推到
+
∞
(
n
)
+\infty(\sqrt n)
+∞(n)
具体细节见代码:
//#pragma GCC optimize(2)
//#pragma GCC optimize("Ofast","inline","-ffast-math")
//#pragma GCC target("avx,sse2,sse3,sse4,mmx")
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<vector>
#include<set>
#include<map>
#include<stack>
#include<queue>
#define ll long long
#define inf 0x3f3f3f3f
#define int ll
#define endl '\n'
#define IOS ios::sync_with_stdio(0); cin.tie(0); cout.tie(0)
using namespace std;
int read()
{
int res = 0,flag = 1;
char ch = getchar();
while(ch<'0' || ch>'9')
{
if(ch == '-') flag = -1;
ch = getchar();
}
while(ch>='0' && ch<='9')
{
res = (res<<3)+(res<<1)+(ch^48);//res*10+ch-'0';
ch = getchar();
}
return res*flag;
}
const int maxn = 1e6+5;
const int mod = 1e9+7;
const double pi = acos(-1);
const double eps = 1e-8;
int n,cnt,pri[maxn],sp1[maxn],sp2[maxn],sq;
int w[maxn],tot,id1[maxn],id2[maxn],g1[maxn],g2[maxn];
bool vis[maxn];
void get_pri(int n)
{
for(int i = 2;i <= n;i++)
{
if(!vis[i])
{
pri[++cnt] = i;
sp1[cnt] = (sp1[cnt-1]+i)%mod;
sp2[cnt] = (sp2[cnt-1]+i*i%mod)%mod;
}
for(int j = 1;j <= cnt && i*pri[j] <= n;j++)
{
vis[i*pri[j]] = true;
if(i%pri[j] == 0) break;
}
}
}
int qpow(int a,int b)
{
int res = 1;
while(b)
{
if(b&1) res = res*a%mod;
a = a*a%mod;
b >>= 1;
}
return res;
}
const int inv2 = qpow(2,mod-2),inv6 = qpow(6,mod-2);
ll S(int i,int j)
{
if(pri[j] >= i) return 0;
ll pos = i<=sq ? id1[i] : id2[n/i];
ll res = ((g2[pos]-g1[pos]+mod)%mod-(sp2[j]-sp1[j]+mod)%mod+mod)%mod; //质数部分贡献
for(int k = j+1;k<=cnt && pri[k]*pri[k]<=i;k++) //合数部分贡献
{
ll pe = pri[k];
for(int e = 1;pe <= i;e++,pe = pe*pri[k]) //不能取模
{
ll x = pe%mod;
res = (res+x*(x-1)%mod*(S(i/pe,k)+(e>1))%mod)%mod;
}
}
return res;
}
signed main()
{
n = read(); sq = sqrt(n);
get_pri(sq);
for(int l = 1,r;l <= n;l = r+1)
{
r = min(n,n/(n/l));
w[++tot] = n/l%mod; //取余方便计算下述的g(n,0)
g1[tot] = (w[tot]*(w[tot]+1)%mod*inv2%mod-1+mod)%mod; //减一是为了减去第一项,因为 1 不是质数
g2[tot] = (w[tot]*(w[tot]+1)%mod*(2*w[tot]+1)%mod*inv6%mod-1+mod)%mod;
w[tot] = n/l;
if(w[tot] <= sq) id1[w[tot]] = tot;
else id2[n/w[tot]] = tot;
}
for(int j = 1;j <= cnt;j++) //g(n,j)
for(int i = 1;i<=tot && pri[j]*pri[j]<=w[i];i++)
{
ll tmp = w[i]/pri[j];
ll pos = tmp <= sq ? id1[tmp] : id2[n/tmp];
g1[i] = (g1[i]-pri[j]*(g1[pos]-sp1[j-1]+mod)%mod+mod)%mod;
g2[i] = (g2[i]-pri[j]*pri[j]%mod*(g2[pos]-sp2[j-1]+mod)%mod+mod)%mod;
}
printf("%lld\n",(S(n,0)+1)%mod);
return 0;
}