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

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

埃式筛法:给定一个正整数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);
    }
}

你可能感兴趣的:(数的因子(约数),素数相关)