线性筛

定义:

线性筛是在O(nlog(log(n)))的Eratosthenes筛法基础上改进得到的一种算法,可以O(n)求得1~n中的所有素数,因此经常在一些与素数有关的问题中用于预处理。对线性筛进行扩展之后,又可以计算出几乎所有的积性函数。

PS:素数密度据一位老师所说是O(n/ln(n))的。

原理:

线性筛的基础思想与Eratosthenes筛法相同,不再赘述。Eratosthenes筛法在运行过程中,含有多个质因子的数会被重复筛掉。例:30=2*3*5,则它会被2,3,5都筛一遍,而这显然是没有必要的。线性筛正是从这个地方着手改进得来。

线性筛的核心思想就是在筛的过程中,每个合数都只被它的最小质因子筛去。这样就能保证每个合数都只被筛一次,复杂度为O(n)。

下面是线性筛的代码:

void prime()
{
   int i,j;
   p[1]=1;
   for(i=2;i<=n;i++)
   {
      if(!p[i]){b[++tot]=i;}
      for(j=1;j<=tot&&i*b[j]<=n;j++)
      {
         p[i*b[j]]=1;
         if(i%b[j]==0){break;}
      }
   }
}

可以发现,线性筛与Eratosthenes筛的区别仅仅是多了一条语句。下面来证明这条语句能使每个合数都只被它的最小质因子筛去。

证明:由p[i*b[j]]=1;又由每个合数都只被它的最小质因子筛去可知,b[j]是i*b[j]的最小质因子。又因为b数组是单调增的,如果i中有b[j]这个质因子,则b[j+1],b[j+2]......一定都不是i*b[j+1],i*b[j+2],......的最小质因子,因此在i基础上的筛没有必要进行下去,直接跳出循环。

至此,我们便实现了O(n)筛出1~n的所有素数。

例题:

Luogu P3383 【模板】线性筛素数 题目链接

题解:

线性筛裸题,直接使用即可。这道题用Eratosthenes筛也可以过,但速度差异明显。

扩展:积性函数的计算

定义:

对于一个数论函数f(x),gcd(a,b)=1,如果有f(a*b)=f(a)*f(b),则称f(x)具有积性。

常见的积性函数:欧拉函数φ,莫比乌斯函数μ,约数幂次和dk. ,gcd(n,k)(k为给定整数)

原理:

在原有线性筛中额外求一个数组h,h[i]表示i的最小质因子的幂次。即:x=p1a1+p2a2+......,(ai!=0,p1<p2<......)则h[x]=p1a1

在线性筛结束之后,从1~n线性递推,由gcd(h[i],i/h[i])=1,f(i)=f(h[i])*f(i/h[i])。

当i=pk 时需要特殊处理,一般从该积性函数的定义就可以直接计算。例:φ(pk )=p-pk-1

至此,我们便实现了线性筛的扩展。

代码(线性筛求φ):

#define LL long long

void prime()
{
   LL i,j,k;
   p[1]=1;phi[1]=1;
   for(i=2;i<maxn;i++)
   {
      if(!p[i]){b[++tot]=i;h[i]=i;}
      for(j=1;j<=tot&&i*b[j]<maxn;j++)
      {
         p[i*b[j]]=1;h[i*b[j]]=b[j];
         if(i%b[j]==0){h[i*b[j]]=h[i]*b[j];break;}
      }
   }
   for(i=1;i<=tot;i++){for(j=b[i];j<maxn;j*=b[i]){phi[j]=j-j/b[i];}}
   for(i=2;i<maxn;i++){phi[i]=phi[h[i]]*phi[i/h[i]];}
}

posted @ 2017-08-24 18:49  青石巷  阅读(552)  评论(0编辑  收藏  举报