埃式筛法(素数筛) + 区间素数筛 + 应用

来源:互联网 发布:cpu温度测试软件 编辑:程序博客网 时间:2024/06/07 06:49

埃拉托斯特尼筛法,简称埃氏筛或爱氏筛

埃式筛法:给定一个正整数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, 所以就要用到埃式筛法.
代码:

bool ispri[maxn];   //是不是素数.int prime[maxn];    //第几个素数.int 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]){            prime[p++] = i;            for(int j=i*2;j<=n;j+=i)                ispri[j] = false;        }    }    return p;}

简单应用 :HDU — 1262
思路: 先对范围内的素数进行打表,然后因为是要选出相邻最近的两个数,所以需要从中间开始找.
(这道题很水, 直接找也能过, 复杂度应该合适)
代码如下:

/** @Cain*/const int maxn=1e6+5;bool ispri[maxn];   //是不是素数.int prime[maxn];    //第几个素数.int 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]){            prime[p++] = i;            for(int j=i*2;j<=n;j+=i)                ispri[j] = false;        }    }    return p;}void solve(){    getpri(maxn);    int i,n;    while(~scanf("%d",&n)){        for(i=n/2;i>=0;i--){   //从中间开始找,如果能找到,则肯定是相邻最近的两个素数,所以输出.            if(ispri[i] && ispri[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既不是素数也不是合数.
板子:

/** @Cain*/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);}

代码解释版:

/** @Cain*/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

/** @Cain*/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);    }}
原创粉丝点击