关于线性筛的研究

(伟大思想大作业)

关于线性筛的研究

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

posted @ 2022-05-09 22:40  zhongzero  阅读(43)  评论(0编辑  收藏  举报