素数筛选

朴素埃式素数筛法

  • 枚举≤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;
}

 

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值