埃拉托斯特尼筛法,简称埃氏筛或爱氏筛
埃式筛法:给定一个正整数n(n<=10^6),问n以内有多少个素数?
做法:做法其实很简单,首先将2到n范围内的整数写下来,其中2是最小的素数。将表中所有的2的倍数划去,表中剩下的最小的数字就是3,他不能被更小的数整除,所以3是素数。再将表中所有的3的倍数划去……以此类推,如果表中剩余的最小的数是m,那么m就是素数。然后将表中所有m的倍数划去,像这样反复操作,就能依次枚举n以内的素数,这样的时间复杂度是O(nloglogn)
例如 : 一些题目中求1-n的素数有多少个, 如果直接筛, 复杂度为O(n*√n), 当n达到1e6是必定会T, 所以就要用到埃式筛法.
代码:
const int maxn = 1e6+5;
bool ispri[maxn]; //是不是素数.
int pri[maxn]; //第几个素数. pri[0] 表示这个范围内素数的个数.
void getpri(int n) { //返回1-n的素数个数.
Fill(ispri, true); // 这样可以直接mem
ispri[0] = ispri[1] = false;
int p = 0;
for(int i = 2 ; i <= n ; i ++) {
if(ispri[i]) {
pri[++p] = i;
for(int j = i*2 ; j <= n ; j += i)
ispri[j] = false;
}
}
pri[0] = p;
}
简单应用 :HDU — 1262
思路: 先对范围内的素数进行打表,然后因为是要选出相邻最近的两个数,所以需要从中间开始找.
(这道题很水, 直接找也能过, 复杂度应该合适)
代码如下:
const int maxn = 1e6+5;
bool ispri[maxn]; //是不是素数.
int pri[maxn]; //第几个素数.
void getpri(int n) { //返回1-n的素数个数.
Fill(ispri, true);
ispri[0] = ispri[1] = false;
int p = 0;
for(int i = 2 ; i <= n ; i ++) {
if(ispri[i]) {
pri[++p] = i;
for(int j = i*2 ; j <= n ; j += i)
ispri[j] = false;
}
}
pri[0] = p;
}
void solve()
{
getpri(maxn);
int i,n;
while(~scanf("%d",&n)){
for(i=n/2;i>=0;i--){ //从中间开始找,如果能找到,则肯定是相邻最近的两个素数,所以输出.
if(pri[i] && pri[n-i])
break;
}
printf("%d %d\n",i,n-i);
}
}
高级应用 : 区间素数筛:给定两个正整数l, r( 1<= L < R <= 10^12, R - L <=10^6),请问[L, R]内有多少个素数?
主要思想:对于数R 以内的合数的最小质因数不会超过√R(反正我是当结论来背的). 所以先求出1-1e6以内的素数(因为1e6的平方刚好是1e12就题目的范围), 再用这些素数去筛出l - r之间的合数,剩下的就是L- R之间的素数了. 二次筛法 …
区间长度只有1e6,所以在存的时候虽然不能直接存每个数,但是可以加个偏移量 L (仔细想想),这样便可存的下 .
注意1既不是素数也不是合数.
板子:
const int maxn = 1e6+5;
bool pri[maxn];
bool ispri[maxn];
void getpri() {
Fill(pri,true);
pri[0] = pri[1] = false;
for(int i = 2 ; i <= maxn ; i++) {
if(pri[i]) {
for(int j = 2*i ; j <= maxn ; j += i)
pri[j] = false;
}
}
}
void seg_getpri(ll L,ll R)
{
Fill(ispri,true);
if(1ll - L >= 0) ispri[1-L] = false;
for(ll i=2;i*i <= R;i++){
if(pri[i]){
for(ll j = max( (L+i-1) / i, 2LL)*i; j<=R ; j+=i)
ispri[ j-L ] = false;
}
}
}
void solve(){
ll L,R;
scanf("%lld%lld",&L,&R);
getpri();
seg_getpri(L,R);
int cnt=0;
for(int i=0;i<=R-L;i++){
if(ispri[i])
cnt++;
}
printf("%d\n",cnt);
}
代码解释版:
const int maxn = 1e6+5;
bool pri[maxn]; //保存1-1e6的素数.
bool ispri[maxn]; //ispri[i-L]=true代表i是素数 //加了个偏移量L.
void getpri() { // 预处理素数.
Fill(pri,true);
pri[0] = pri[1] = false;
for(int i = 2 ; i <= maxn ; i++) {
if(pri[i]) {
for(int j = 2*i ; j <= maxn ; j += i)
pri[j] = false;
}
}
}
void seg_getpri(ll L,ll R) //[L,R]区间筛
{
Fill(ispri,true);
if(1ll - L >= 0) ispri[1-L] = false; //易错因为1不是素数也不是合数,这也是区间筛的一个易错bug
for(ll i=2;i*i <= R;i++){
if(pri[i]){ //也是坑点, 因为L可能比i小, 但是不能从1开始取. 必须从2开始.
//素数的倍数落在L-R区间的要筛掉.
//(L+i-1)/i 得到最接近 L 的 i 的倍数, 最低是i的2倍, 然后筛选(这样j就都在L,R里内了)
for(ll j = max( (L+i-1) / i, 2LL)*i; j<=R ; j+=i)
ispri[ j-L ] = false;
}
}
}
void solve(){
ll L,R;
scanf("%lld%lld",&L,&R);
getpri();
seg_getpri(L,R);
int cnt=0;
for(int i=0;i<=R-L;i++){
if(ispri[i])
cnt++;
}
printf("区间[%lld,%lld]里的素数个数: %d\n",L,R,cnt);
}
例题 poj – 2689
//题意:输入区间[L,U],其中L和U为 int 范围的整数,区间最大为1000000. 求出[L,U]中,相邻素数之差最大和最小的素数对. 当存在多个时,输出较小的素数对
//思路: 就是区间筛, 这个范围还小一点, 数只用枚举到5e5. 然后在区间(还是要1e6长)素数中跑一遍数一下就可以了.
AC Code
const int maxn = 1e6+5;
bool pri[maxn];
bool ispri[maxn];
void getpri()
{
Fill(pri,true);
pri[0] = pri[1] = false;
for(int i = 2 ; i <= maxn/2 ; i ++) {
if(pri[i]){
for(int j = 2*i ; j <= maxn/2 ; j += i)
pri[j]=false;
}
}
}
void seg_getpri(ll L,ll R)
{
Fill(ispri,true);
if(1ll - L >= 0) ispri[1-L] = false;
for(ll i=2;i*i <= R;i++){
if(pri[i]){
for(ll j = max( (L+i-1) / i, 2LL)*i; j<=R ; j+=i)
ispri[ j-L ] = false;
}
}
}
void solve(){
ll L,R;
while(~scanf("%lld%lld",&L,&R)){
getpri();
seg_getpri(L,R);
int maxx = -inf, minn = inf;
int pos = -1,x1,y1,x2,y2;
for(int i=0;i<=R-L;i++){
if(ispri[i]){
if(pos == -1){
pos = i;
continue;
}
if(maxx < i - pos){
maxx = i - pos;
x1 = pos+L; //把偏移量加上就行了.
y1 = i+L;
}
if(minn > i - pos){
minn = i - pos;
x2 = pos+L;
y2 = i+L;
}
pos = i;
}
}
if(maxx == -inf) printf("There are no adjacent primes.\n");
else printf("%d,%d are closest, %d,%d are most distant.\n",x2,y2,x1,y1);
}
}