朴素埃式素数筛法
- 枚举≤n的每一个数x,把x倍数都筛选掉。
int vis[N]; // 素数标记,0为素数,1为合数
memset(vis, 0, sizeof(vis));
for(int i = 2; i <= n; i++) { // 枚举基
for(int j = i*2; j <= n; j += i) { // 枚举倍数
vis[j] = 1;
}
}
优化埃式素数筛法
- 只枚举质数,去筛选
- 枚举质因数时,只用枚举到sqrt(n)
- 枚举倍数从i*i开始枚举。因为之前的都已经被筛掉。 比如枚举7,2*7已经被14筛掉了,3*7倍3筛掉了。就是比i*i小的都被比i小的质因数筛掉 了。
int vis[N]; // 素数标记,0为素数,1为合数
memset(vis, 0, sizeof(vis));
int m = sqrt(n);
for(int i = 2; i <= m; i++) { // 枚举质因子
if(!vis[i]) {
for(int j = i*i; j <= n; j += i) { // 枚举倍数
vis[j] = 1;
}
}
}
欧拉素数筛选
- prime[ ] 数组中的素数是递增的,当算到i%prime[j] == 0直接break。因为 i*prime[ j+1 ] 这个合数肯定被 prime[ j ] 乘以某个数筛掉。因为i中含有prime[ j ]。
- 例如:i = prime[j] * tmp; 那么i * prime[j+1] = prime[j] * (tmp * prime[j+1])。也就是是说当i遍历到tmp * prime[j+1]时,i * prime[j+1]这个数就会被筛掉。所以这样可以省去很多多余的操作。
代码:
int p[N]; // 素数列表
int cnt; // 素数个数
memset(vis, 0, sizeof(vis));
cnt = 0;
for(int i = 2; i <= n; i++) { // 枚举倍数
if(!vis[i]) p[cnt++] = i;
for(int j = 0; j < cnt && i*p[j] <= n; j++) { // 枚举质因子
vis[i*p[j]] = 1;
if(i%p[j] == 0) break;
}
}
区间素数筛选
lightoj1197
- 如果直接在区间[a,b]中筛选,因为a,b会很大,所以数组开不了,而且耗空间。
- 一般给的区间不会很大(1e6左右)。
- 所以可以先筛选出[0,sqrt(b)]区间内的素数,然后用这个区间再去筛选[a,b]区间的素数
- 那怎么用[0,sqrt(b)]区间内的素数的数去筛选呢?所以我们先把这个区间内的素数打表,再枚举。每次枚举它的倍数,这个倍数必须在区间[a,b]当中。枚举到的vis置为true,最后vis为false的就表示是素数。
- 问题1:为了保证每次枚举都在[a,b]中,减少时间复杂度,我们可以先算出k = a / prime,如果能够整除就从k的倍数开始枚举,不能够整除从k+1的倍数开始枚举。这里有一个细节需要注意,如果k = 1,说明prime就是左区间,那么循环一开始就把a给筛掉了,但是a是素数呀!所以就k++;
- 问题2:怎么存储[a,b]呢?直接把区间全部右移。如果筛的数是x,那么vis[x-a] = true。最后找素数的时候就在[0,b-a]之间。
- 问题3:如果a = 1,结果会如何?我们知道1是不会被任何数给筛选掉的。如果筛选[1,5]区间的素数,最后找的是[0,4]之间的素数,算出来的是3,但是实际答案是2。因为1映射到了0,但是0没有被筛选掉。所以干脆a++。这样2就映射到了0,1没考虑了。
代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
int const N = 1e6 + 10;
int prime[N],vis[N],len;
ll a,b;
void prime_table(){ //筛选[1,1e6]之间的素数
for(int i=2;i<N;i++){
if(!vis[i]) prime[len++] = i;
for(int j=0;j<len && i*prime[j]<N;j++){
vis[i*prime[j]] = true;
if(i % prime[j] == 0) break;
}
}
}
int solve(){
memset(vis,0,sizeof(vis));
if(a == 1) a = 2;
for(ll i=0;i<len && prime[i]*1ll*prime[i]<=b;i++){//之前我用1ll*prime[i]*prime[i]WA
ll k = a / prime[i] + (a % prime[i] > 0); //原来1ll是在后半部分先算出结果再转换
if(k == 1) k = 2;
for(ll j=k;j*1ll*prime[i]<=b;j++) vis[j*1ll*prime[i]-a] = true;
}
int sum = 0;
for(ll i=0;i<=b-a;i++) if(!vis[i]) sum++;
return sum;
}
int main(){
int T;
prime_table();
int cnt = 0;
scanf("%d",&T);
while(T--){
scanf("%lld%lld",&a,&b);
printf("Case %d: %d\n",++cnt,solve());
}
return 0;
}