怎么筛质数?有一种很简单的方法,1到n枚举,是质数就把他所有的倍数打一个合数标记,然后就完了。但是我们有一个问题,比如说30,它会被2筛掉,被3筛掉,被5筛掉。这样复杂度就不会很优秀,我们有什么做法让30只被2筛,不被3和5筛掉呢?
于是我们换一种筛法,据说叫做欧拉筛。就是对于每一个数,我们用小于它的质数乘上它,然后把那个数筛掉。重要的是,如果这个数可以整除当前质数,就break掉。这样我们就很惊奇地发现,每个数我们都筛且只筛了一次。这是为什么呢,我们再画一个图。
上面筛不到,下面一定有一个把它筛掉对不对?而且这么做有一个性质,我们可以保证每一个数都被它最小的质数筛掉。这样,我们发现积性函数是非常好筛的。比如说 miu m i u (莫比乌斯函数),我们分三种情况讨论一下:
1.x是一个质数: miu(x)=−1 m i u ( x ) = − 1
2.x最小的质数只有一次,记它为 p,miu(x)=miu(xp)∗miu(p) p , m i u ( x ) = m i u ( x p ) ∗ m i u ( p ) 因为是积性函数, xp x p 与 p p 互质
3.x最小的质数不止一次, miu(x)=0 m i u ( x ) = 0
很显然,我们线筛的时候有一个质数表作为副产品。
贴一波代码
void shai()
{
vis[1]=1;miu[1]=1;//特殊处理1的情况
for(int i=2;i<=n;i++)
{
if(vis[i]==0) p[++cnt]=i,miu[i]=-1;//质数情况,miu为-1
for(int j=1;j<=cnt&&i*p[j]<=n;j++)
{
if(i%p[j]==0)//可以整除的话,最小质数至少次数为2,所以miu为0
{
vis[i*p[j]]=1;
miu[i*p[j]]=0;
break;//保证复杂度为O(1)
}
miu[i*p[j]]=miu[i]*miu[p[j]];//不能整除,两个互质,则满足积性函数条件
vis[i*p[j]]=1;
}
}
}
所有线筛的板子都是这样的,可以思考一下 phi p h i (欧拉函数)的筛法。
然后我们还可以拓展一下,我们不止可以筛积性函数,还可以筛一些非积性函数。
比如这个函数: f(x)=x f ( x ) = x 分解质因数的最高次幂,特殊的 f(1)=0 f ( 1 ) = 0
1.质数情况: f(x)=1 f ( x ) = 1
2.x最小的质数只有一次,记它为 p,f(x)=max{f(xp),1} p , f ( x ) = m a x { f ( x p ) , 1 }
3.x最小的质数不止一次,记它为 p p ,记它的次数为 c c : f(x)=max{f(xp),c} f ( x ) = m a x { f ( x p ) , c }
那么问题来了,我们如何才能 O(1) O ( 1 ) 查询 c c 咧?所以我们还要和 f f 一起筛 c c
1.质数情况: c(x)=1 c ( x ) = 1
2. x x 最小的质数只有一次: c(x)=1 c ( x ) = 1
3. x x 最小的质数不止一次,记它为 p:c(xp)+1 p : c ( x p ) + 1
然后我们把质数 ,f,c , f , c 一起筛,就可以 O(n) O ( n ) 得到 f f 了。
总结一下,线性筛法不止可以筛积性函数,还可以筛一些有特点的非积性函数,这些函数一般与因数或者质因数有关。判断一个函数是否可以线性筛,我们不需要判断它是不是积性函数,我们只要判断它在以上三种情况的取值就行。但是在某些情况十分复杂的时候,我们很难笔算出各情况的取值。于是我们可以 O(n2) O ( n 2 ) 打表找一找规律,有时候有奇效。可能笔算很难推的式子规律却很简单。不过值得注意的是,打表也不一定找得到规律,找规律的时候一定要大开脑洞。具体情况还是要具体分析,对于不同的题目可能用不同的方法,会简化很多。