“临近深渊的时刻,钟表将停止转动。”

这里定义,本文中提及的 $p$ 在无特殊说明的前提下皆表示质数

线性筛质数

这个应该很简单啦,使用线性筛即可。背板子。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
bool is[MAXN],pr[MAXN],tot;
inline void sieve(int n)
{
memset(is,1,sizeof(is));
is[1]=0;
for(int i=2;i<=n;++i)
{
if(is[i]) pr[++tot]=i;
for(int j=1;j<=tot&&i*pri[j]<=n;++j)
{
is[i*pri[j]]=0;
if(i%pri[j]==0) break;
}
}
}

有意思的练习题

UVA1644

这道题有够水的,甚至于 $\mathcal O(n\sqrt n)$ 都能勉强卡过。

不过我们还是先一遍线性筛,然后二分查找第一个比 $n$ 大的质数。

时间复杂度 $\mathcal O(n\log\frac{sum}{\ln sum}+sum)$ ,其中 $sum=2\times 10^6$ 。

AC Code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
#include<cstdlib>
#include<algorithm>
#include<ctime>
#include<iomanip>
#include<queue>
#include<stack>
#include<map>
#include<vector>
#define gh() getchar()
#define re register
typedef long long ll;
const int MAXN=2e6+1;
template<class T>
inline void read(T &x)
{
x=0;
char ch=gh(),t=0;
while(ch<'0'||ch>'9') t|=ch=='-',ch=gh();
while(ch>='0'&&ch<='9') x=(x<<3)+(x<<1)+(ch^48),ch=gh();
if(t) x=-x;
}
template<class T,class ...T1>
inline void read(T &x,T1 &...x1)
{
read(x),read(x1...);
}
template<class T>
inline void write(T x)
{
if(x<0) putchar('-'),x=-x;
if(x>9) write(x/10);
putchar(x%10+'0');
}
template<class T>
inline bool checkMax(T &x,T &y)
{
return x<y?x=y,1:0;
}
template<class T>
inline bool checkMin(T &x,T &y)
{
return x>y?x=y,1:0;
}
int Pri[MAXN],Tot;
bool Is[MAXN];
inline void Sieve()
{
memset(Is,1,sizeof(Is));Is[1]=0;
for(int i=2;i<MAXN;++i)
{
if(Is[i]) Pri[++Tot]=i;
for(int j=1;j<=Tot&&i*Pri[j]<MAXN;++j)
{
Is[i*Pri[j]]=0;
if(i%Pri[j]==0) break;
}
}
}
int N;
int main()
{
// freopen("sieve.in","r",stdin);
// freopen("sieve.out","w",stdout);
Sieve();
read(N);
while(N)
{
if(Is[N]) puts("0");
else
{
int l=1,r=Tot;
while(l<r)
{
int mid=(l+r)>>1;
if(Pri[mid]>N) r=mid;
else l=mid+1;
}
write(Pri[l]-Pri[l-1]),puts("");
}
read(N);
}
return 0;
}
/*
10
11
27
2
492170
0
*/

P1621

这道题教会我一个道理,最优的筛法并不一定最优。

这道题,如果使用欧拉筛的话,每一个数只会被自己最小的质因子筛掉。而我们的 $p$ 可以是任一质因子。而正巧,我们有一个复杂度没那么优秀,但依然优秀的筛法——埃氏筛。

埃氏筛保证每一个数都会被自己的所有质因子筛到。所以我们直接求取 $1\sim b$ 的所有质数,并在其中处理满足 $(i,j)\ge p$ 条件的,并把它们在并查集中合并。

令当前质数为 $p_0\ge p$ ,我们枚举 $2p_0\sim\min(kp_0,b)$ ,显然,$kp_0$ 和 $(k-1)p_0$ 有一个质因子为 $p_0\ge p$ ,所以 $kp_0$ 和 $(k-1)p_0$ 必在一个集合。所以我们扫一遍,然后把上述的两个连在一起就好了。

时间复杂度如果我没记错的话,是 $\mathcal O(b\log\log b)$ 。

AC Code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
#include<cstdlib>
#include<algorithm>
#include<ctime>
#include<iomanip>
#include<queue>
#include<stack>
#include<map>
#include<vector>
#define gh() getchar()
#define re register
typedef long long ll;
const int MAXN=1e5+1;
template<class T>
inline void read(T &x)
{
x=0;
char ch=gh(),t=0;
while(ch<'0'||ch>'9') t|=ch=='-',ch=gh();
while(ch>='0'&&ch<='9') x=(x<<3)+(x<<1)+(ch^48),ch=gh();
if(t) x=-x;
}
template<class T,class ...T1>
inline void read(T &x,T1 &...x1)
{
read(x),read(x1...);
}
template<class T>
inline void write(T x)
{
if(x<0) putchar('-'),x=-x;
if(x>9) write(x/10);
putchar(x%10+'0');
}
template<class T>
inline bool checkMax(T &x,T &y)
{
return x<y?x=y,1:0;
}
template<class T>
inline bool checkMin(T &x,T &y)
{
return x>y?x=y,1:0;
}
bool Is[MAXN];
int a,b,p,Fa[MAXN];
int Anc(int x)
{
return Fa[x]==x?x:Fa[x]=Anc(Fa[x]);
}
inline void Sieve(int n)
{
for(int i=2;i<=n;++i)
{
if(!Is[i])
if(i>=p)
{
for(int j=i<<1;j<=n;j+=i)
{
Is[j]=1;
if(j-i>=a&&Anc(j)!=Anc(j-i))
Fa[Anc(j)]=Fa[Anc(j-i)];
}
}
else for(int j=i<<1;j<=n;j+=i) Is[j]=1;
}
}
int main()
{
// freopen("sieve.in","r",stdin);
// freopen("sieve.out","w",stdout);
read(a,b,p);
for(int i=a;i<=b;++i) Fa[i]=i;
Sieve(b);
int ans=0;
for(int i=a;i<=b;++i) if(Fa[i]==i) ++ans;
write(ans);
return 0;
}
/*
10 20 3
*/

线性筛/欧拉筛

在 $\mathcal O(n)$ 的时间内筛出积性函数 $f$ 的 $1\sim n$ 的值,使用前提是 $f(p^k)$ 能够在 $\mathcal O(1)$ 的时间内计算。

对于合数 $n$ 的分解为 $n=\prod p_i^{k_i}$ ,记录最小的质因子 $p_1$ 的贡献部分为 $g(n)=p_1^{k_1}$ 。作为线性筛的前提,每一个合数都只能被自己的最小质因子筛中。

所以当 $n$ 被 $x\cdot p$ 筛到时,若 $x$ 是 $p$ 的倍数,则 $g(n)=g(x)\times p$ ,否则 $g(n)=p$ 。若有 $g(n)=n$ ,说明 $n=p_1^{k_1}$ ,此时 $f(n)$ 可以使用 $\mathcal O(1)$ 的时间计算,否则有 $f(n)=f(g(n))\times f(\frac{n}{g(n)})$ 。

线性筛欧拉函数

首先,欧拉函数的计算公式 $\varphi(n)=n\times\prod(1-\frac{1}{p_i})$ 。

有性质:

  1. $\varphi(p)=p-1$ ;
  2. $\varphi(x\times p)=\varphi(x)\times p,p\mid x$ ,此时可以直接 break;
  3. $\varphi(x\times p)=\varphi(x)\times(p-1),p\not\mid x$ 。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
bool is[MAXN],pr[MAXN],phi[MAXN],tot;
inline void sieve(int n)
{
memset(is,1,sizeof(is));
is[1]=0,phi[1]=1;
for(int i=2;i<=n;++i)
{
if(is[i]) pr[++tot]=i,phi[i]=i-1;
for(int j=1;j<=tot&&i*pri[j]<=n;++j)
{
is[i*pri[j]]=0;
if(i%pri[j]==0)
{
phi[i*pri[j]]=phi[i]*pri[j];
break;
}
phi[i*pri[j]]=phi[i]*(pri[j]-1);
}
}
}

线性筛莫比乌斯函数

莫比乌斯函数的计算公式太难写了,就不写了

首先,初始化 $\mu(1)=1$ ,然后线性筛,筛出:

  1. $\mu(i\times pri[j])=0,i\% pri[j]=0$ ,并执行 break;
  2. $\mu(i\times pri[j])=-1\times\mu(i),i\% pri[j]\ne 0$ 。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
bool is[MAXN],pr[MAXN],mu[MAXN],tot;
inline void sieve(int n)
{
memset(is,1,sizeof(is));
is[1]=0;mu[1]=1;
for(int i=2;i<=n;++i)
{
if(is[i]) pr[++tot]=i,mu[i]=-1;
for(int j=1;j<=tot&&i*pri[j]<=n;++j)
{
is[i*pri[j]]=0;
if(i%pri[j]==0)
{
mu[i*pri[j]]=0;
break;
}
mu[i*pri[j]]=-mu[i];
}
}
}

线性筛主要还是用于预处理的,作为解一些与质数,最大公约数,或者莫比乌斯反演的题的预处理。


杜教筛

在 $\mathcal O(n^{\frac{2}{3}})$ 的时间复杂度内求出一个积性函数 $f$ 的前缀和。

定义 $S(n)=\sum_{i<n}f(i)$ ,找两个易于求前缀和的积性函数 $g,h$ 满足 $f\ast g=h$ ,则有:

$\sum_{i=1}^nh(i)=\sum_{i=1}^n\sum_{d\mid i}f(d)g(\frac{i}{d})$

$\sum_{i=1}^nh(i)=\sum_{d=1}^ng(d)\sum_{i=1}^{\lfloor\frac{n}{d}\rfloor}f(i)$

$\sum_{i=1}^nh(i)=g(1)\cdot S(n)+\sum_{d=2}^ng(d)S(\lfloor\frac{n}{d}\rfloor)$

$g(1)\times S(n)=\sum_{i=1}^nh(i)-\sum_{d=2}^ng(d)S(\lfloor\frac{n}{d}\rfloor)$

对于杜教筛的实现,可以线性筛预处理出 $f$ 的前 $\mathcal O(\sqrt n)$ 项前缀和,再递归计算 $S(n)$ ,用 $\operatorname{Hash}$ 和 $\operatorname{map}$ 记忆化计算结果。

常用的构造 $f\ast g=h$ 有 $\mu\ast I=\epsilon,\varphi\ast I=id,id\ast \mu=\varphi$ 等。

求解

对于数论函数 $f$ ,我们的任务就是构造一个 $S(n)$ 关于 $S(\lfloor\frac{n}{i}\rfloor)$ 的递推式以在低于线性时间的复杂度内求解 $f$ 的前缀和。

对于任意一个数论函数 $g$ 必满足:

从而得到递推式:

$\displaystyle g(1)S(n)=\sum_{i=1}^n(f\ast g)(i)-\sum_{i=2}^ng(i)S\left(\left\lfloor\frac{n}{i}\right\rfloor\right)$

模板

莫比乌斯函数前缀和

$\displaystyle S_{\mu}(n)=\sum_{i=1}^n\epsilon(i)-\sum_{i=2}^nS_{\mu}(\lfloor\frac{n}{i}\rfloor)=1-\sum_{i=2}^nS_{\mu}(\lfloor\frac{n}{i}\rfloor)$

使用整数分块,直接计算 $\mathcal O(n^{\frac{3}{4}})$ ,预处理前 $n^{\frac{2}{3}}$ 项,剩余复杂度为 $\displaystyle\mathcal O(\int^{n^{\frac{1}{3}}}_{0}\sqrt{\frac{n}{x}}dx)=\mathcal O(n^{\frac{2}{3}})$

对于无法直接使用 $\text{Hash}$ 的较大数值,考虑 std::map

欧拉函数前缀和

可以使用杜教筛 $\varphi(n)$ 的前缀和,也可以使用莫比乌斯反演。这里考虑杜教筛:

有 $\varphi\ast 1=id$

结束。

实现

AC Code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
#include<bits/stdc++.h>
const int MAXN=2e6+10;
typedef long long ll;
ll Test,N,Pri[MAXN],Cur,Mubi[MAXN],S_M[MAXN];
bool Vis[MAXN];
std::map<ll,ll>Mp_M;
ll mobius_S(ll x)
{
if(x<MAXN) return S_M[x];
if(Mp_M[x]) return Mp_M[x];
ll ret=1ll;
for(ll i=2,j;i<=x;i=j+1)
{
j=x/(x/i);
ret-=mobius_S(x/i)*(j-i+1);
}
return Mp_M[x]=ret;
}
ll phi_S(ll x)
{
ll ret=0ll;
for(ll i=1,j;i<=x;i=j+1)
{
j=x/(x/i);
ret+=(mobius_S(j)-mobius_S(i-1))*(x/i)*(x/i);
}
return (ret-1)/2+1;
}
inline void Sieve(int N)
{
Mubi[1]=1;
for(int i=2;i<=N;++i)
{
if(!Vis[i])
Pri[++Cur]=i,Mubi[i]=-1;
for(int j=1;j<=Cur&&i*Pri[j]<=N;++j)
{
Vis[i*Pri[j]]=1;
if(i%Pri[j]==0)
{
Mubi[i*Pri[j]]=0;
break;
}
Mubi[i*Pri[j]]=-Mubi[i];
}
}
for(int i=1;i<=N;++i) S_M[i]=S_M[i-1]+Mubi[i];
}
int main()
{
std::cin>>Test;
Sieve(MAXN-1);
while(Test--)
{
scanf("%lld",&N);
printf("%lld %lld\n",phi_S(N),mobius_S(N));
}
}
/*
6
1
2
8
13
30
2333
*/

Min_25 筛

也是一种求积性函数前缀和的筛法,时间复杂度为 $\mathcal O(\frac{n^{\frac{3}{4}}}{\log_2n})$ ,空间复杂度 $\mathcal O(\sqrt n)$ 。要求质数 $p$ 处的函数值是一个多项式。该筛法要求 $F(p^k)$ 是一个多项式(如果不是,可能会算不出来)。

记 $cnt$ 为 $\le n$ 的质数个数,$p_i$ 表示第 $i$ 个质数,$R(x)$ 表示 $x$ 的最小质因子,$F(x)$ 是一个积性函数。(这里我们定义除法默认下取整)

$\text{Min}_\text{25}$ 筛的实现分为两个部分。

Part 1

考虑求解一个式子:

定义一个二元函数 $g$ ,表示为:

这里假设函数 $f$ 的计算方法和都和 $F$ 中素数的计算方法一致。例如 $F(x)=\varphi(x),f(x)=x-1$ 等之类的。

这里的 $f$ 被要求为一个完全积性函数,这是执行筛法的要求。而我们要求的 $\sum_{i=1}^{cnt}F(p_i)=g(n,cnt)$ ,所需要的是 $g(n,cnt)$ ,而 $g$ 中只有素数的函数值,所以和原式是相等的。

而 $n$ 以内的数最小质因子显然不会超过 $\sqrt{n}$ ,所以也可以写成 $g(n,m),p^2_m\ge n$ 。

作为筛法,应当考虑 $g(n,j)$ 的递推计算。若有 $p_j^2\ge n$ ,则 $g(n,j)=g(n,j-1)$ ,因为不会存在 $R(i)\ge p_j$ 的数。

那么,我们可以得出 $g(n,j)$ 的转移式:

对于 $otherwise$ 的探讨:考虑 $g(n,j-1)$ 和 $g(n,j)$ 的差别,根据 $g$ 的定义,不难看出,$g(n,j)$ 比 $g(n,j-1)$ 缺少的部分就是那些以 $p_j$ 为最小质因子的合数的函数值。所以减去的那部分就是刚刚提及的那部分。

我们尝试把 $g$ 的空间复杂度压到 $\mathcal O(\sqrt n)$ 。

$g$ 的第一维用于存储 $\frac{n}{i}$ 这些位置,且其转移只存在于 $n\to \frac{n}{p_j}$ ,而向下取整的除法可结合,即 $\frac{\frac{x}{a}}{b}=\frac{x}{ab}$ ,无论怎么除,都只需要 $g(n,m)$ ,只会用到 $\mathcal O(\sqrt n)$ 个关键位置。

$g$ 的第二维用于转移 $j\to j-1$ ,而与此同时第一维不断缩小,我们只需要 $g(n,m)$ ,可以直接压掉第二维后滚动数组。

下面是 $F(x)=1,g(n,m)$ 求 $n$ 以内的质数个数的代码:

1
2
3
4
5
6
7
8
9
10
11
int sqN=ceil(sqrt(n));
for(int i=1;i<=Tot;++i) g[i]=w[i]-1;//g(w[i],0)
for(int j=1;j<=Cnt;++j)
for(int i=1;i<=Tot&&Pri[j]*Pri[j]<=w[i];++i)
{
int k=w[i]/Pri[j];
if(k<=sqN) k=id[0][k];
else k=id[1][n/k];
g[i]-=g[k]-j+1;
}
write(g[1]);

其中

1
2
3
4
Tot表示关键点个数
w[i]表示从大到小的第i个关键点
g[i]表示当前的g(w[i],j)
id[0/1][k]分别表示k和n/k是第几个关键点

从而完成 $\sum\limits_{x=1}^{n}F(x)[x\in Prime]$ 的计算。

时间复杂度为 $\mathcal O(\frac{n^{\frac{3}{4}}}{\log_2n})$ 。

Part 2

对于求解 $F(x)$ 在所有位置的函数值之和 $ans=\sum\limits_{i=1}^{n}F(i)$ 。定义一个二元函数为 $S$ :

则有 $ans=S(n,1)+F(1)$ 。

把 $S$ 的计算拆成素数部分和合数部分,素数部分显然是 $g(n,cnt)-\sum\limits_{i=1}^{j-1}F(p_i)$ 。而对于合数部分,我们需要进一步计算。

枚举最小质因子 $p_k$ 以及其次幂 $e$ ,不重不漏地计算 $R(i)\ge p_j$ 的合数。枚举幂次,使得 $p_k^e$ 被提及之后,就不会存在质因子 $p_k$ ,函数值就可以直接与括号外相乘。对于 $p_k^e,e\ge 2$ 作为合数,不会被枚举,所以我们需要手动加上。

这样可以免去记忆化,直接递归计算。边界是 $n=1,\vee p_j>n,S(n,j)=0$ 。

时间复杂度与 $\text{Part 1}$ 相同,也可以是 $\mathcal O(n^{1-\epsilon})$ 。