筛法
计算机中常使用筛法来计算素数。筛法的基本思想都是从一个区间中删去不符合要求的数,从而得到一个符合要求的区间,所以形象的称呼为筛法。
埃拉托斯特尼筛法(埃筛)
它又叫线性筛。埃筛可以快速获取小于等于n的素数集合。首先明确一点:对于任意合数x,它必有一个小于等于sqrt(x)的因子,因为如果两者均大于sqrt(x)那么其积必大于x。那么判断x是否为素数只需要判断2~sqrt(x)能否与x整除即可,如果一直计算到sqrt(x)都没有出现x的因数那x就是一个素数了。
埃筛基于以下原理:对于一个素数n(n>1),它的k(k>1)倍数一定是一个合数。
假设要计算100以内的素数,从2开始遍历。
- 将2在100以内的所有倍数标记为合数。
- 指针后移,检查3是否为合数。3不是合数,那么把3的所有倍数也标记为合数。
- 指针后移,检查4是否为合数。4已经被标记为合数,跳过它。
- 重复上述流程一直到sqrt(100)。
事实上埃筛可以进行优化,即每次标记从大于等于自身的倍数开始进行。如5,只要标记5 × 5, 5 × 6, 5 × 7…为合数,因为5 × 2, 5 × 3, 5 × 4…已经被之前出现的数的倍数标记过了。
//时间复杂度O(sqrt(n)) void findPrimes(int n) { int p[n]; p[0] = 0;//放个0占位 int s = sqrt(n); for (int i = 2; i <= s; i++)//从2到sqrt(x)计算 if (p[i] != 0) //如果i不是合数 { int a = i * i;//从大于等于自身的倍数开始 while (a <= n) { p[a] = 0;//标记为合数 a += i;//跳向下一个倍数 } } }
欧拉筛
埃筛的思想很好理解,但可能出现同一个合数被不同质因子重复筛去的可能。欧拉筛作为埃筛的优化版本,减去了这种冗余的计算,让每个合数只被它的最小质因子筛掉。所以他又叫快速线性筛。
void findPrimes(int n) { int cnt = 0; int b[n], p[n];//b[i]=1表示i为合数, p用来存素数集合 for (int i = 2; i <= n; i++) { if (!b[i])//判断并记录素数 p[++cnt] = i; for (int j = 1; j <= cnt && i*p[j] <= n; j++)//开始筛 { b[i*p[j]] = 1;//① if (i % p[j] == 0) break;//② } } }
①:此处i*p[j]表示将第一个素数到目前最后一个素数的i倍标记为合数。如果说传统埃筛是每步将一个素数的所有倍数标记,那欧拉筛就是每步将所有素数的一个倍数标记,结果相同方式不同,只是为了后续优化好计算做的改变。
②:此处就是欧拉筛的优化点。当i%p[j]==0时i是p[j]的倍数,即i=k × p[j]。此时的p[j]就是i的最小质因子。如果此时不break,继续去算i × p[j+1],那么有等式i × p[j+1]=k × p[j] × p[j+1]。当i等于k × p[j+1]时就会重复计算。所以此处break可以避免重复筛同一个合数。
区间筛
区间筛针对的问题与埃筛和欧拉筛不尽相同。它针对的问题是: 给定整数 a 和 b,请问区间 [a,b) 内有多少个素数?
上文提到对于任意一个合数n,它必有一个小于等于sqrt(n)的因子。那么推广一下,对于在区间[a,b)的任意合数,它的最小质因子也一定不超过sqrt(b)。思路很明显了,为了筛到大区间[a,b)中的所有素数,可以先做[2,sqrt(b))这个小区间内的素数表,然后根据倍数筛去大区间内的合数即可。素数表由欧拉筛得到。
void sectionSieve(ll l,ll r) { //f为小区间素数表,t为大区间素数表 for(ll i = 2; i*i < r; i++) { if(!f[i]) { for(ll j = i*2; j*j < r; j += i) //i是素数,所以从2*i枚举小区间,j是l之后第一个i的倍数,故j+i也是i的倍数 f[j] = 1; for(ll j= (l+1)/i*i; j < r; j += i) //(l+1)/i 得到最接近l的i的倍数,最低是i的2倍,然后筛选 t[j-l]=1; } } }
最后t[l~r]中就是素数表。也可以用下标偏移来做优化。
一些题目
计蒜客A1956-SUM
题目大意:
如果一个整数不能被拆分成除1外的任何一个平方数,那么它被称为无平方因子数。举个例子:6(=2·3)就是一个无平方因子数,但12(=4·3)就不是,因为4(=2^2)是一个平方数。一些整数可以被拆分成两个无平方因子数之积(这种拆分可能不止一种)。举个例子:6=6·1=2·3=3·2=1·6。当a!=b时我们认为n=ab与n=ba是两种拆分。f(n)表示n的拆分方案数,请你计算sum[f(1~n)]。
输入:
第一行包含一个整数T(T<=20),表示T组测试数据。
对任意一组数据,第一行包含一个整数n(n<=2e7)。
输出:
对任意一组数据,输出答案sum[f(1~n)]。
样例输入: 2 5 8
样例输出: 8 14
思路:
分析题目特性,可以得到以下规律。对任意的n有:
- n=1时
显然,f(1) = 1·1 = 1。 - n是素数时
对任意的素数,必然只有n = 1·n,所以此时f(n) = 2。 - n = i · Pj时
这个要分为两种情况讨论
1. Pj^2|i 。因为Pj是一个素数,所以i至少等于Pj^2,那么n中至少含有三个Pj,不论怎么拆分都会产生平方因子。 此时f(n) = 0。
2. 除上述情况之外,可以看做先拆i,i = a·b,那么n=(a·b)·Pj。所以此时f(n)=f(n/Pj)。
根据上述思路,先用欧拉筛预处理数据,然后直接查询。时间复杂度是O(n)+O(1)。
#include <cstdio> #include <cstring> #include <iostream> #include <algorithm> #define get(x) scanf("%d", &x) #define set(a,b) memset(a, b, sizeof(a)) #define rep(a, b, c) for (int a = b; a <= c; a++) using namespace std; typedef long long ll; const int LIM = ll(2e7)+5; int t, n, cnt; int p[LIM], f[LIM]; bool vis[LIM]; void eulerSieve() { vis[0] = vis[1] = 1; f[1] = 1;//n=1时只有一种 for (int i = 2; i < LIM; i++) { if(!vis[i]) { p[++cnt] = i; f[i] = 2;//n是素数的情况 } for (int j = 1; j <= cnt && ll(i)*p[j] < LIM; j++) { vis[i*p[j]] = 1; if (i % p[j] == 0) { f[i * p[j]] = (i / p[j] % p[j] == 0 ? 0 : f[i / p[j]]); break; } else f[i * p[j]] = f[i] * f[p[j]]; } } for (int i = 1; i < LIM; i++) f[i] += f[i-1]; } int main() { eulerSieve(); get(t); while(t--) { get(n); printf("%d\n", f[n]); } return 0; }
洛谷1835-素数密度
题目:
给定区间[L,R],计算区间内素数个数。1<=L,R<=int,R-L<=1e6。
输入:
L与R。
输出:
一个整数表示[L,R]中的素数个数。
样例输入: 2 11
样例输出: 5
板子题,主要是找手感。
#include <cstdio> #include <cstring> #include <iostream> #include <algorithm> #define get(x) scanf("%d", &x) #define lget(x) scanf("%lld", &x) #define set(a,b) memset(a, b, sizeof(a)) #define rep(a, b, c) for (int a = b; a <= c; a++) using namespace std; typedef long long ll; const int LIM = 1000001; ll l, r; int cnt; int f[LIM], t[LIM]; void init(ll l,ll r) { for(ll i = 2; i*i < r; i++) { if(!f[i]) { for(ll j = i*2; j*j < r; j += i) f[j] = 1; for(ll j= (l+1)/i*i; j < r; j += i) //i是素数,所以从2*i枚举。j是l之后第一个i的倍数,故j+i也是i的倍数 t[j-l]=1; } } } int main() { lget(l); lget(r); init(l, r+1); for(ll i = l; i <= r; i++) if(!t[i-l]) cnt++; cout << cnt; return 0; }
评论
还没有任何评论,你来说两句吧!