关于线性筛的研究
(伟大思想大作业)
关于线性筛的研究
1.线性筛质数
2.线性筛积性函数
3.线性筛的应用
1.线性筛质数
引入
我们考虑这样一个问题,给定一个数 \(n\) ,判断其是否为质数
即可转化为判断一个数是否存在非平凡(非1且非n)的因子
其中一种朴素算法是枚举小于n的所有数,判断它是否为n的因子,复杂度为$ O(n)$
这种算法可以被优化到\(O(\sqrt n)\) ,方法为枚举时,只需枚举 \(2\)~$\sqrt n $ 的数是否为 \(n\) 的因子即可,原因是对于n的任意一个非平凡的因子 \(k\) ,一定存在非平凡的因子 \(n/k\) (\(n/k\) 可能等于 \(k\) ),而 \(k\) 与 \(n/k\) 中至少有一个数小于 \(\sqrt n\) ,即若n存在非平凡因子,那么至少有一个非平凡因子 \(\leq \sqrt n\)
另外一种复杂度更优的算法是Miller-Rabin,它是通过随机化算法来判断一个数是否为质数,一般在10次左右的随机后就能得到基本正确的答案,这里不是本文重点,不对其进行展开
筛法
我们不妨再提出这样一个问题,给定一个数 \(n\) ,要求快速判断 \(2\) ~ \(n\) 中每个数是否为质数
如果我们通过上述方法一个个判断,做法正确性上没有问题,但就算是复杂度较优的Miller-Rabin算法,它的复杂度和常数还是有些偏大了
我们是否能找到一种其他算法快速判断 \(2\) ~ \(n\) 中每个数是否为质数呢
这里就提出了筛法的概念
筛法是一种算法,其主要方式是通过筛掉不合法的,把合法的留下来。比如说这道题中即要求我们筛掉不是质数的,把是质数的留下来
我们指出质数和合数的区别,其中一个本质区别是 合数存在非平凡的因子,而质数不存在非平凡的因子,由此我们便可以由此获得一种筛的方法。
考虑对于每个数 \(x\) \((x>1)\),易知 \(2x\) ,\(3x\) ,\(4x\) ,\(5x\) ...都不为质数。而对于每个数 \(y\) ,若对于 \(\forall x \in (2,y-1)\) ,在把所有的 \(2x\) ,\(3x\) ,\(4x\) ,\(5x\) ...都筛掉后,y仍没被筛掉,则 \(y\) 为质数,反之 \(y\) 不为质数
由此我们便得到这样一种算法
for(i=2;i<=n;i++){
if(vis[i]==false)prim[++cnt]=i;//将i标记为质数
for(j=2;j*i<=n;j++){
vis[i*j]=true;//表示i*j这个数不为质数,被筛掉了
}
}
考虑复杂度
\(n+\frac{n}{2}+\frac{n}{3}+\frac{n}{4}+...+\frac{n}{n}=\sum_{i=1}^{n}\frac{n}{i}\)
为调和级数,复杂度为 \(O(nlogn)\)
埃氏筛法
这种方法复杂度其实已经不错了,但还有可以优化的地方,我们这里再引入一个埃氏筛法(the Sieve of Eratosthenes)
我们发现对于每个合数,它的非平凡因子中肯定有至少一个质因子,因此我们对上面的算法进行优化
对于每个数 \(y\) ,若对于 \(\forall p \in (2,y-1)\) 且 \(p\) 为质数,在把所有的 \(2p\) ,\(3p\) ,\(4p\) ,\(5p\) ...都筛掉后,y仍没被筛掉,则 \(y\) 为质数,反之 \(y\) 不为质数
for(i=2;i<=n;i++){
if(vis[i]==false){
prim[++cnt]=i;//将i标记为质数
for(j=2;j*i<=n;j++){
vis[i*j]=true;//表示i*j这个数不为质数,被筛掉了
}
}
}
考虑复杂度
\(\sum_{i\leq n且n为质数}\frac{n}{i}\)
这里有一个定理 Mertens' theorems,\(\sum_{i\leq n且n为质数}\frac{1}{i}-lnlnn\) 在 \(n-> +\infin\) 时收敛于一个常数 \(M(M \approx 0.2614972)\)
因此复杂度为 \(O(nloglogn)\)
欧拉筛
虽然上面一种算法已经十分优秀了,但我们不禁还是想问,是否有线性的筛法可以筛出所有质数?
我们观察发现,上面的算法都存在这样一个问题,即一个合数 \(x\) 可能被多次筛出,如 \(x=30\) 时,使用埃氏筛法,会发现 $ i=2,j=15 ; i=3,j=10 ; i=5,j=6 $ 时都会把 \(x\) 筛掉,使得复杂度难以保持线性,那我们如果能找到一种筛法,使得每个合数都能被恰好不重不漏的筛一次,那这种算法便是线性的
这就要引出一个叫做欧拉筛的算法
下面先贴出它的实现方法
for(i=2;i<=n;i++){
if(vis[i]==false)prim[++cnt]=i;//将i标记为质数
for(j=1;j<=cnt&&i*prim[j]<=n;j++){
vis[i*prim[j]]=true;//表示i*prim[j]这个数不为质数,被筛掉了
if(i%prim[j]==0)break;
}
}
它的核心想法是,对于任意一个合数 \(x\) ,假设把它分解质因数为 \(x=\prod_{1 \leq i \leq d}{p_i}^{k_i}\) ,其中 \(p_i<p_{i+1} (\forall i \in (1,d-1))\) ,我们考虑上面的写法,不妨考虑 $x $ 的所有非平凡因子 \(i\) ,让 \(x\) 只从 \(i=x/p_1\) 处转移过来。
我们可以发现对于上述代码的做法,其和一般筛法最大的不同就是 if(i%prim[j]==0)break; 这句话。
对于 \(i=x/p_1\) 的情况,容易得知 \(prim[j]=p_1\) 之前时,还未执行break操作,而是在 \(prim[j]=p_1\) 执行完之后执行break操作,所以可以知道 \(x\) 可以从 \(i=x/p_1\) 处转移过来
而对于其他情况,由这段代码的实现可知,\(x\) 只能从 \(i=x/p_j\) 处转移过来,若 \(j\) 不为 \(1\) ,则 \(i\) 中必定包含因子 \(p_1\) ,则当 \(prim[j]=p_1\) 执行后,就将执行break操作,从而 \(x\) 无法从 \(i=x/p_j\) 通过 \(prim[j]=p_j\) 转移过来(注意 \(prim[]\) 中记录的质数是单调递增的)
由于每个合数只会被筛恰好一次,而break操作执行次数也是 \(O(n)\) 的,总复杂度 \(O(n)\)
从而我们便得到了一个优秀的线性筛法
2.线性筛积性函数
我们先给出积性函数的定义
若存在一个函数 \(F(x)\),满足对于任意互质的整数 \(a\) 和 \(b\) ,有性质 \(F(ab)=F(a)F(b)\) ,则称 \(F(x)\) 为线性函数
我们考虑对欧拉筛进行扩展,如果一个线性函数可以快速地通过一些方法求出\(F(p_i^k)\) ,如在 \(F(p_i^{k-1})\) 的基础上推出,那个便可以线性晒出一个积性函数的前 \(n\) 项
for(i=2;i<=n;i++){
if(vis[i]==false)prim[++cnt]=i,F[i]= i为p的情况,low[i]=i;
for(j=1;j<=cnt&&i*prim[j]<=n;j++){
vis[i*prim[j]]=true;
if(i%prim[j]==0){
low[i*prim[j]]=low[i]*prim[j];
if(low[i]==i)F[i*prim[j]]= i为p^k的情况;
else F[i*prim[j]]=F[i/low[i]]*F[low[i]*prim[j]];
break;
}
else {
F[i*prim[j]]=F[i]*F[prim[j]];
low[i*prim[j]]=prim[j];
}
}
}
我们引出了一个 \(low[]\) 数组,\(low[x]=p_i^{k_i}\) ,其中 \(x=\prod_{1 \leq i \leq d}{p_i}^{k_i}\)
\(low[x]\) 数组的求法如下:
1.当 \(x\) 为质数时,\(low[x]=x\)
2.当 \(x\) 是从 \(i*prim[j]\) 处转移过来时,且 \(prim[j]\) 为 $ i$ 的因子 ,则 \(low[i*prim[j]]=low[i]*prim[j]\)
3.当 \(x\) 是从 \(i*prim[j]\) 处转移过来时,且 \(prim[j]\) 不为 $ i$ 的因子 ,则 \(low[i*prim[j]]=prim[j]\)
而对于 \(F[x]\) 的求法如下:
1.当x为质数时,可由 \(F[x]\) 自身性质快速推出
2.当 \(x\) 是从 \(i*prim[j]\) 处转移过来时,且 \(prim[j]\) 为 $ i$ 的因子 ,考虑两种情况。
若 \(i\) 为 \(p_1^{k_1}\) 的形式 ,则 \(low[i*prim[j]]\) 可由 \(F[x]\) 自身性质快速推出(易知当 \(x=i*prim[j]\) 时,\(p_1\) 即为 \(prim[j]\) )
不然,则易知 $ low[i]=p_1^{k_1} $ ,\(i/low[i]\) 与 \(low[i]*prim[j]\) 互质,又由 \(F[x]\) 的线性性可知 \(F[i*prim[j]]=F[i/low[i]]*F[low[i]*prim[j]]\)
3.当 \(x\) 是从 \(i*prim[j]\) 处转移过来时,且 \(prim[j]\) 不为 $ i$ 的因子 ,由于 \(i\) 与 \(prim[j]\) 互质 ,又由 \(F[x]\) 的线性性可知, \(F[i*prim[j]]=F[i]*F[prim[j]]\)
由此,我们便得到了线性筛出一部分积性函数的方法
3.线性筛的应用
莫比乌斯函数
莫比乌斯函数 \(μ[]\) 满足
\(μ[n]=(−1)k(n=p1p2…pk)\)
\(μ[n]=0(∃P^2|n)\)
\(μ[n]=1(n=1)\)
其为一个十分常见的积性函数,在对重复计算的去除等方面有着极其重要的应用
我们考虑对其进行线性筛,上述代码实现方法毫无疑问可以完美解决,但由于莫比乌斯函数自身的性质,对它的筛法仍存在另一种更加优美的写法
mu[1]=1;
for(i=2;i<=n;i++){
if(vis[i]==false)prim[++cnt]=i,mu[i]=-1;
for(j=1;j<=cnt&&i*prim[j]<=n;j++){
vis[i*prim[j]]=true;
if(i%prim[j]==0){
mu[i*prim[j]]=0;
break;
}
else mu[i*prim[j]]=mu[i]*mu[prim[j]];
}
}
很显然,当 \(prim[j]\) 为 \(i\) 的因子时,\(mu[i*prim[j]]=0\)
欧拉函数
欧拉函数是小于n的正整数中与n互质的数的数目.
欧拉函数也是一个积性函数,它在数论上有着极其重要的应用
同莫比乌斯函数,由于它自身的性质,对它的筛法仍存在另一种更加优美的写法,这里贴出代码,但不对其进行展开,留给读者自己思考正确性
for(i=2;i<=n;i++){
if(vis[i]==false)prim[++cnt]=i,phi[i]=i-1;
for(j=1;j<=cnt&&i*prim[j]<=n;j++){
vis[i*prim[j]]=true;
if(i%prim[j]==0){
phi[i*prim[j]]=phi[i]*prim[j];
break;
}
else phi[i*prim[j]]=phi[i]*phi[prim[j]];
}
}
线性筛约数个数
即 对于1~n中每一个数 \(x=\prod_{1 \leq i \leq d}{p_i}^{k_i}\) ,求 \(f(i)=\prod_{i=1}^d(k_i+1)\)
该函数也为线性函数,也可用上述方法线性筛出。当然由于它自身的性质,它也存在更加简便的写法,这里不将其写出,留给读者自行思考
除此之外,还能 线性筛约数和 ,与线性筛其他线性函数,这里不再一一说明
总结
线性筛在数论的理论研究和实际使用上都有重要的贡献,本文期待能带给读者了解线性筛的实现方式与一些可行的使用条件,并期待能给读者一些关于线性筛延展的思考
钟林昊
2022.1.8