这个筛法是最朴素的筛法了,可以在 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]中的质数个数。 n≤m≤1012,m−n≤106 n ≤ m ≤ 10 12 , m − n ≤ 10 6 。
首先我们会发现,如果区间中某个数不是质数,那么它必然有一个小于1E6的质因子。于是我们可以把[1,1E6]范围内的质数全部筛出来,然后用这些质数去筛区间[n,m]中的数字,复杂度应该是 O((m−n)loglog(m−n)) 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,j,都有f(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互质的数的个数。定理:
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]中所有数字的欧拉函数值了。
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];
}
}
于是我们的线性筛就可以用来做题了!
杜教筛是求积性函数前缀和的一种方法。考虑如下的题目:
请求出如下表达式的值( n≤109 n ≤ 10 9 ):
求出所有的有序数对 (i,j)使i,j互质且i≤n,j≤n,n≤109 ( i , j ) 使 i , j 互 质 且 i ≤ n , j ≤ n , n ≤ 10 9 。即如下算式:
求出如下表达式的值( n≤109 n ≤ 10 9 ):
在大部分情况下,杜教筛已经可以解决很多问题了,但是仍然有很多毒瘤题解决不了。这个时候就可以试一试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个质数小的质数,我们要把这些特殊情况去掉:
//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,y都有f(x)f(y)=f(xy) x , y 都 有 f ( x ) f ( y ) = f ( x y ) ,我们可以用g数组筛出这些东西。只需要稍微修改一下上面的递推式即可:
求如下表达式的值( m≤109, n≤1012 m ≤ 10 9 , n ≤ 10 12 ):
推荐Min_25筛题目:51nod奇怪的数学题,loj简单的函数