埃氏筛法+线性筛法+杜教筛+min25筛总结

埃氏筛法

这个筛法是最朴素的筛法了,可以在 O(nloglogn) O ( n l o g l o g n ) 的时间内(基本 O(n) O ( n ) )筛出[1,n]中所有素数。实现非常简单,从2开始遍历,对于每个质数都暴力算出它的所有倍数并筛掉,根据欧拉的调和级数定理,这个时间是 O(nlogn) O ( n l o g n ) 级别的,但是只有质数才需要计算倍数,然后不知怎么回事复杂度就变成 O(nloglogn) O ( n l o g l o g n ) 了。

const int maxn = 100005, N = 100000;
int vis[maxn];
for(int i = 2; i <= N; i++) if(!vis[i])
    for(int j = i + i; j <= N; j += i) vis[j] = 1;

最终vis数组中值为0的即为素数。

应用:给定n,m,求区间[n,m]中的质数个数。 nm1012,mn106 n ≤ m ≤ 10 12 , m − n ≤ 10 6
首先我们会发现,如果区间中某个数不是质数,那么它必然有一个小于1E6的质因子。于是我们可以把[1,1E6]范围内的质数全部筛出来,然后用这些质数去筛区间[n,m]中的数字,复杂度应该是 O((mn)loglog(mn)) O ( ( m − n ) l o g l o g ( m − n ) ) 。代码就不放了。

线性筛(欧拉筛法)

顾名思义,它可以在线性时间内筛出区间内质数及积性函数的值。但是在n小于1E6的时候甚至比埃氏筛法慢(因为常数大),于是它的主要功能就变成了求积性函数的值。
考虑如何做到线性。如果我们把每个数字都用它的最小质因子筛去的话就行了,但是怎么做呢?
从小到大枚举每个数字,再枚举当前已经筛出来的质数,筛掉数字乘质数的值。如果数字是当前质数的倍数就可以退出了,因为质数继续枚举就不是最小质因子了。因此此时效率为 O(n) O ( n ) 。代码如下:

const int maxn = 100005, N = 100000;
int vis[maxn], prime[maxn], cnt;
for(int i = 2; i <= N; i++){
    if(!vis[i]) prime[++cnt] = i;
    for(int j = 1; j <= cnt; j++){
        int p = prime[j], mul = p * i;
        if(mul > N) break;
        vis[mul] = 1;
        if(i % p == 0) break;
    }
}

上面说过了,这个东西还可以筛出积性函数的值。积性函数定义为对于任意互质的数对 i,jf(i)f(j)=f(ij) i , j , 都 有 f ( i ) f ( j ) = f ( i j ) 。我们上面实际上算出了每个数的最小质因子,当然也可以算出积性函数的值了,当然我们需要计算任意的质数以及单个质数的幂次对应的f的值,因为这样才可以利用质因数分解计算出任意数字的f。

const int maxn = 100005, N = 100000;
int f[maxn], pw[maxn], vis[maxn], prime[maxn], cnt;
//pw[i]为i最小质因子的次数
for(int i = 2; i <= N; i++){
    if(!vis[i]){
        prime[++cnt] = i;
        pw[i] = 1;
        f[i] = ...;//要求给出i为质数时f(i)的值
    }
    for(int j = 1; j <= cnt; j++){
        int p = prime[j], mul = p * i;
        if(mul > N) break;
        vis[mul] = 1;
        if(i % p == 0){
            f[mul] = f[i / pow(p, pw[i])] * f[pow(p, pw[i] + 1)];//要求给出i为质数的整数次幂时f的值
            pw[mul] = pw[i] + 1;
            break;
        } else pw[mul] = pw[i] * pw[p];
    }
}

当然了,具体计算的时候可以用一些方法把pow去掉。接下来就来列举一些积性函数的求值:

欧拉函数

φ(n)= φ ( n ) = 小于等于n且与n互质的数的个数。定理:

φ(n)=npi1pi,n=peii,pi φ ( n ) = n ∏ p i − 1 p i , n = ∏ p i e i , p i 为 质 数

因此我们会发现,若p为n的一个质因子,则有 φ(np)=pφ(n) φ ( n p ) = p · φ ( n ) .当p为质数时 phi(p)=p1 p h i ( p ) = p − 1 。于是就可以愉快地写筛法了:

const int maxn = 100005, N = 100000;
int vis[maxn], phi[maxn], prime[maxn], cnt;
phi[1] = 1;
for(int i = 2; i <= N; i++){
    if(!vis[i]){
        prime[++cnt] = i;
        phi[i] = i - 1;
    }
    for(int j = 1; j <= cnt; j++){
        int p = prime[j], mul = p * i;
        if(mul > N) break;
        vis[mul] = 1;
        if(i % p == 0){
            phi[mul] = phi[i] * p;
            break;
        } else phi[mul] = phi[i] * phi[p];
    }
}

于是我们就可以在 O(n) O ( n ) 的时间内处理出[1,n]中所有数字的欧拉函数值了。

莫比乌斯函数

μ(n)=1(1)k0n=1n=p1p2pk,piothers μ ( n ) = { 1 n = 1 ( − 1 ) k n = p 1 p 2 ⋯ p k , p i 为 质 数 0 o t h e r s

当n含有平方因子时函数值为0,n为质数时函数值为1,于是也可以线性筛了。

const int maxn = 100005, N = 100000;
int vis[maxn], mu[maxn], prime[maxn], cnt;
mu[1] = 1;
for(int i = 2; i <= N; i++){
    if(!vis[i]){
        prime[++cnt] = i;
        mu[i] = -1;
    }
    for(int j = 1; j <= cnt; j++){
        int p = prime[j], mul = p * i;
        if(mul > N) break;
        vis[mul] = 1;
        if(i % p == 0) break;//mul含有平方因子,mu值为0
        mu[mul] = -mu[i];//实际上是mu[mul] = mu[i] * mu[p];
    }
}

于是我们的线性筛就可以用来做题了!

例题

i=1nj=1ngcd(i,j) ∑ i = 1 n ∑ j = 1 n g c d ( i , j )

求出上式的值,其中 n106 n ≤ 10 6
根据欧拉函数的性质 d|nφ(d)=n ∑ d | n φ ( d ) = n ,可以化简上式得到
i=1nj=1ngcd(i,j)=i=1nj=1nd|i,d|jφ(d)=d=1nφ(d)ni2 ∑ i = 1 n ∑ j = 1 n g c d ( i , j ) = ∑ i = 1 n ∑ j = 1 n ∑ d | i , d | j φ ( d ) = ∑ d = 1 n φ ( d ) ⌊ n i ⌋ 2

于是我们只需要计算出区间中欧拉函数的值就行了,复杂度 O(n) O ( n ) 。当然这道题可以利用下面说的杜教筛进一步优化,把复杂度降到 O(n23) O ( n 2 3 )

杜教筛

杜教筛是求积性函数前缀和的一种方法。考虑如下的题目:
请求出如下表达式的值( n109 n ≤ 10 9 ):

ϕ(n)=i=1nφ(i) ϕ ( n ) = ∑ i = 1 n φ ( i )

考虑使用欧拉函数的性质 d|nφ(d)=n ∑ d | n φ ( d ) = n ,得到 φ(n)=nd|n,d<nφ(d) φ ( n ) = n − ∑ d | n , d < n φ ( d ) 。带入得到:
ϕ(n)=i=1nid|i,d<iφ(d) ϕ ( n ) = ∑ i = 1 n i − ∑ d | i , d < i φ ( d )

考虑枚举 id i d 的值进行计算:
ϕ(n)=n(n+1)2i=2nd=1niφ(d)=n(n+1)2i=2nϕ(ni) ϕ ( n ) = n ( n + 1 ) 2 − ∑ i = 2 n ∑ d = 1 ⌊ n i ⌋ φ ( d ) = n ( n + 1 ) 2 − ∑ i = 2 n ϕ ( ⌊ n i ⌋ )

我们会发现后面那个向下取整的式子中只有 O(n) O ( n ) 种不同的值,因此可以整除分块,带上记忆化搜索复杂度是 O(n34) O ( n 3 4 ) 。但是我们可以用线性筛预处理出前 O(n23) O ( n 2 3 ) 个欧拉函数的前缀和,杜教筛就可以取到最好的复杂度,为 O(n23) O ( n 2 3 )
同时我们会发现,杜教筛不仅求出了 ϕ(n) ϕ ( n ) ,也求出了任意的 phi(ni) p h i ( ⌊ n i ⌋ ) 。这个东西对于做题是非常有帮助的,比如上面最大公约数之和那题,我们就可以在外面的求和也整除分块一下,利用上面的值在较好的复杂度内完成算法。

例题:互质数对

求出所有的有序数对 (i,j)使i,jin,jn,n109 ( i , j ) 使 i , j 互 质 且 i ≤ n , j ≤ n , n ≤ 10 9 。即如下算式:

i=1nj=1n[gcd(i,j)=1] ∑ i = 1 n ∑ j = 1 n [ gcd ( i , j ) = 1 ]

考虑莫比乌斯函数的性质 d|nμ(d)=[n=1] ∑ d | n μ ( d ) = [ n = 1 ] ,原式转化为
i=1nj=1nd|i,d|jμ(d)=d=1nμ(d)ni2 ∑ i = 1 n ∑ j = 1 n ∑ d | i , d | j μ ( d ) = ∑ d = 1 n μ ( d ) ⌊ n i ⌋ 2

于是我们的目标是求出莫比乌斯函数的前缀和。仿照上面的做法:
M(n)=i=1nμ(i)=i=1n[i=1]d|i,d<iμ(d)=1i=2nM(ni) M ( n ) = ∑ i = 1 n μ ( i ) = ∑ i = 1 n [ i = 1 ] − ∑ d | i , d < i μ ( d ) = 1 − ∑ i = 2 n M ( ⌊ n i ⌋ )

于是我们就顺利地在 O(n23) O ( n 2 3 ) 的时间内求出了莫比乌斯函数前缀和,上面那题也解决了。
其实我们也可以直接利用欧拉函数的定义计算这道题,答案其实就是 2φ(n)1 2 φ ( n ) − 1

例题:最小公倍数之和

求出如下表达式的值( n109 n ≤ 10 9 ):

i=1nj=1nlcm(i,j)=i=1nj=1nijgcd(i,j) ∑ i = 1 n ∑ j = 1 n l c m ( i , j ) = ∑ i = 1 n ∑ j = 1 n i j gcd ( i , j )

枚举最大公因数,可以得到( 下面默认除法为向下取整):
d=1ndi=1n/dj=1n/dij[gcd(i,j)=1] ∑ d = 1 n d ∑ i = 1 n / d ∑ j = 1 n / d i j · [ gcd ( i , j ) = 1 ]

f(i)=j=1ij[gcd(i,j)=1]=j=1ijd|i,d|jμ(d)=d|iμ(d)did(id+1)2=i2d|iμ(d)id+μ(d)=i2(φ(i)+[i=1]) 我 们 定 义 f ( i ) = ∑ j = 1 i j · [ gcd ( i , j ) = 1 ] = ∑ j = 1 i j · ∑ d | i , d | j μ ( d ) = ∑ d | i μ ( d ) · d · i d ( i d + 1 ) 2 = i 2 ( ∑ d | i μ ( d ) i d + μ ( d ) ) = i 2 ( φ ( i ) + [ i = 1 ] )

于是题目就可以化简为:
d=1nd(2i=1n/dif(i)1)=d=1ndi=1n/di2φ(i)=d=1ndg(nd) ∑ d = 1 n d ( 2 ∑ i = 1 n / d i · f ( i ) − 1 ) = ∑ d = 1 n d ∑ i = 1 n / d i 2 φ ( i ) = ∑ d = 1 n d · g ( ⌊ n d ⌋ )

于是我们需要计算出g函数的前缀和即可。我们会发现如下性质: g(n)=n3n2d|n,d<nφ(d) g ( n ) = n 3 − n 2 ∑ d | n , d < n φ ( d ) ,于是杜教筛:
G(n)=i=1ng(i)=i=1ni3i2d|i,d<iφ(d)=i=1ni3i=2nd=1n/i(id)2φ(d)=n2(n+1)24i=2nG(ni)i2 G ( n ) = ∑ i = 1 n g ( i ) = ∑ i = 1 n i 3 − i 2 ∑ d | i , d < i φ ( d ) = ∑ i = 1 n i 3 − ∑ i = 2 n ∑ d = 1 n / i ( i d ) 2 φ ( d ) = n 2 ( n + 1 ) 2 4 − ∑ i = 2 n G ( ⌊ n i ⌋ ) · i 2

于是这道题就可以在 O(n23) O ( n 2 3 ) 的时间内解决了。

Min_25筛

在大部分情况下,杜教筛已经可以解决很多问题了,但是仍然有很多毒瘤题解决不了。这个时候就可以试一试Min_25筛(我也不造为啥叫这个名字),复杂度为 O(n34logn) O ( n 3 4 log ⁡ n ) (我也不造为啥是这个复杂度)。
这个筛法常数比较小,内存也比较小,应该算一种挺不错的筛法了。我们以筛出[1,n]中的质数个数为例来说明这个筛法。
首先,他假设了[2,n]中所有数字全是质数,然后用数论办法模拟进行埃氏筛法。其实埃氏筛法中质数大小到 O(n) O ( n ) 的级别时就没有数字可以被筛掉了。先预处理出这些质数,就可以开始Min_25筛了。我们令状态g(n,j)表示当前筛的区间是[1,n],已经筛完了前j个质数时剩下来的数字个数(即所有最小质因子大于第j个质数的数字和所有质数的总个数)。
我们考虑用g(n,j-1)推出g(n,j)。j-1时比j多了一些最小质因子为第j个质数的数字,要把他们减掉,由于g(n,j-1)中还存在比第j个质数小的质数,我们要把这些特殊情况去掉:

g(n,j)=g(n,j1)g(npj,j1)+j1 g ( n , j ) = g ( n , j − 1 ) − g ( ⌊ n p j ⌋ , j − 1 ) + j − 1

可以证明,这样的复杂度是 O(n34logn) O ( n 3 4 log ⁡ n ) 的。

//id1和id2是离散化的数组,tot是小于sqrt(n)的质数个数
//prime是质数数组,num数组记n/i向下取整不同的值,从大到小排序,cnt为这些值的个数。
int sqr = (int)(sqrt(n) + 1);
for(int i = 1; i <= tot; i++){
    int p = prime[i];
    for(int j = 1; j <= cnt && p * p <= num[j]; j++){
        int d = num[j] / p, k = d <= sqr ? id1[d] : id2[n / d];
        g[j] -= g[k] - j + 1;
    }
}

初始值就是把[2,n]中所有数字都当成质数的结果。当然,除了质数个数,我们还可以筛出来很多其它的东西。
f(x) f ( x ) 在把所有数字都当成质数时是完全积性函数,即任意的 x,yf(x)f(y)=f(xy) x , y 都 有 f ( x ) f ( y ) = f ( x y ) ,我们可以用g数组筛出这些东西。只需要稍微修改一下上面的递推式即可:

g(n,j)=g(n,j1)f(pj)(g(npj,j1)i=1j1f(pi)) g ( n , j ) = g ( n , j − 1 ) − f ( p j ) ( g ( ⌊ n p j ⌋ , j − 1 ) − ∑ i = 1 j − 1 f ( p i ) )

考虑如何利用这个东西求出 积性函数 g(x) g ( x ) 的前缀和。首先我们可以通过拆分等方法把所有质数的函数值之和算出来,然后定义S(n,j)为区间[2,n]中所有最小质因子大于等于第j个质数的数字函数之和,我们可以暴力枚举那些数字的最小质因子去计算,即得到
S(n,j)=g(n,|P|)i=1j1g(pi)+kj,p2kn e1,pe+1knS(npek,k+1)g(pek)+g(pe+1k) S ( n , j ) = g ( n , | P | ) − ∑ i = 1 j − 1 g ( p i ) + ∑ k ≥ j , p k 2 ≤ n   ∑ e ≥ 1 , p k e + 1 ≤ n S ( ⌊ n p k e ⌋ , k + 1 ) · g ( p k e ) + g ( p k e + 1 )

我也不造咋证的,这部分暴力计算的复杂度也是 O(n34logn) O ( n 3 4 log ⁡ n ) ,于是我们就愉快的做完了。

例题

求如下表达式的值( m109, n1012 m ≤ 10 9 ,   n ≤ 10 12 ):

i=1nμ(in) ∑ i = 1 n μ ( i n )

首先若n含有平方因子则答案为0,否则我们考虑i的质因子中是否跟n有相同的。首先我们用g筛出质数个数,然后来搞一搞S的递推式(其实如果枚举质数的次数超过1就不用算了,莫比乌斯函数值必然为0):
S(n,j)=g(n,|P|)j+1i=jcnt[pi|n]i=jcntS(npi,i+1) S ( n , j ) = g ( n , | P | ) − j + 1 − ∑ i = j c n t [ p i | n ] − ∑ i = j c n t S ( ⌊ n p i ⌋ , i + 1 )

最后再加上1的情况,然后这道题就愉快地在 O(m34logm+n) O ( m 3 4 log ⁡ m + n ) 的时间内做完了……

推荐Min_25筛题目:51nod奇怪的数学题,loj简单的函数

你可能感兴趣的:(数论)