各种友(e)善(xin)数论总集,从入门到绝望3---线性筛与其最重要的作用

文章目录

  • 例题
  • 埃式筛
  • 欧拉筛
    • 复杂度证明及其性质
  • 欧拉筛筛积性函数

感觉原来那篇文章已经卡的不行了,赶紧搬出来QAQ。

例题

题目描述
【题意】
素数表如下:
2、3、5、7、11、13、17…………
有n次询问,每次问第几个素数是谁?
【输入格式】
第一行n(1<=n<=10000)
下来n行,每行一个整数pi,表示求第pi个素数。1<=pi<=100 0000
【输出格式】
每次询问输出对应的素数。
【样例输入】
3
1
4
7
【样例输出】
2
7
17

线性筛素数有两种,埃式筛和欧拉筛。

埃式筛

我们可以知道,一个数字如果不是质数,那么它的最小质因子一定小于等于这个数字的开平方。

很容易想到,不详细解释。

那么求 1 1 1 b b b的素数的话,我们可以知道, 1 1 1 b b b的合数的最小质因数也是一定小于等于 b \sqrt b b 的,那么我们只需要求出 b \sqrt b b 内的所有素数并且暴力for一遍,空间小,复杂度为 O ( n l o g l o g n ) O(nloglogn) O(nloglogn)

for(int  i=2;i<=sqrt(b);i++)
{
    if(mp[i]==false)//为素数
    {
        for(int  j=i;j<=b;j+=i)mp[j]=true;
    }
}

给出一种神奇卡常版:

for(register  int  i=3;i<=sqrt(m);i+=2)
{
    if(a[i/2]==true)
    {
        for(register  int  j=i*3;j<=m;j+=i*2)a[j/2]=false;
    }
}

但是,还是可以优化的,每次跳的时候从 i 2 i^2 i2开始跳,因为前面的有更小的素数去更新。

for(int  i=2;i<=sqrt(b);i++)
{
    if(mp[i]==false)//为素数
    {
        for(int  j=i*i;j<=b;j+=i)mp[j]=true;
    }
}

这个的神奇卡常版就不加了QAQ。

复杂度正确,貌似是用调和级数来证,我不会QAQ。

欧拉筛

后面题目主要用欧拉筛,复杂度 O ( n ) O(n) O(n)

这个就很神奇了,先给出代码:

#include
#include
#define  N  21000000
using  namespace  std;
int  ins[2100000];//素数表
bool  mp[N];
int  main()
{
	for(int  i=2;i<=20000000;i++)
	{
		if(!mp[i])ins[++ins[0]]=i;//存入素数表
		for(int  j=1;j<=ins[0]  &&  ins[j]*i<=20000000;j++)
		{
			mp[ins[j]*i]=true;
			if(i%ins[j]==0)break;
		}
	}
	int  n;scanf("%d",&n);
	for(int  i=1;i<=n;i++)
	{
		int  x;scanf("%d",&x);
		printf("%d\n",ins[x]);
	}
	return  0;
}

许多人疑问这一句

if(i%ins[j]==0)break;

如果 i n s j ∣ i ins_{j}|i insji,那么就可以break了,Why?那么我们设 i i i d ∗ i n s j d*ins_{j} dinsj,设 k > j k>j k>j i ∗ i n s k = d ∗ i n s j ∗ i n s k = ( d ∗ i n s k ) ∗ i n s j i*ins_{k}=d*ins_{j}*ins_{k}=(d*ins_{k})*ins_{j} iinsk=dinsjinsk=(dinsk)insj,我们可以知道 d ∗ i n s k > d ∗ i n s j d*ins_{k}>d*ins_{j} dinsk>dinsj,那么在以后的循环, i ∗ i n s k i*ins_{k} iinsk会被重复标记,那么此时就可以break了,一句话就这么精妙。

超级卡常版:

for(register  int  i=3;i<=m;i+=2)
{
    if(a[i/2]==true)b[++k]=i;
    for(register  int  j=1;j<=k  &&  b[j]*i<=m;++j)
    {
        a[b[j]*i/2]=false;
        if(i%b[j]==0)break;
    }
}

当然,慎用卡常版。

其实我是乱搞的

复杂度证明及其性质

对于合数 x x x,设其最小的约数 y y y,那么他有没有可能被大于 y y y的约数所 z z z找到呢?

那么就是 x z ∗ z \frac{x}{z}*z zxz,但是 y ∣ x z y|\frac{x}{z} yzx,所以在找到 x x x的时候就会退出,所以每个数字只会被其最小约数找到,所以就是 O ( n ) O(n) O(n)

另外提一下,因为其只能被最小约数筛,所以经常被用来筛积性函数。

还有一个性质也经常被用到:如果 i i i被某个质数 x x x整除(就是进入了 i f if if),那么 i ∗ x i*x ix的某个质数因子的指数一定大于 1 1 1

欧拉筛筛积性函数

关于积性函数的定义可以去看“各种友(e)善(xin)数论总集,从入门到绝望4(狄利克雷卷积与莫比乌斯反演)”

对于一般的积型函数: f ( i ) f ( j ) = f ( i j ) f(i)f(j)=f(ij) f(i)f(j)=f(ij) g c d ( i , j ) = 1 gcd(i,j)=1 gcd(i,j)=1

我们可以对于每个数字 i i i都存一个 l o w low low值,表示的是最小的约数次幂,即对于一个数字拆成: p 1 a 1 . . . p i a i ( p 1 < p 2 < p 3 < . . . ) p_{1}^{a_{1}}...p_i^{a_i}(p_1p1a1...piai(p1<p2<p3<...) p p p都是质数),那么 l o w low low存的就是 p 1 a 1 p_1^{a_1} p1a1

而当我们在找到一个质数的时候,就处理其次幂的函数值(这也是积性函数中最需要推的东西)。

所以一个数字 x x x就可以被拆成 x l o w x \frac{x}{low_x} lowxx l o w x low_x lowx了。

而对于完全积性函数,不用存 l o w low low值,而对于某些具有一定性质的完全积性函数,我们也可以采用某些神奇的仿写省掉 l o w low low,常见的做法就是对于 i i i被某个质数整除和没有被整除的情况分别讨论,如: p h i ( φ ) phi(φ) phi(φ)函数。

具体操作请参照欧拉函数。

你可能感兴趣的:(线性筛素数,算法讲解)