zoukankan      html  css  js  c++  java
  • 素数筛选法

    素数筛选法是一种快速求某个大整数区间内的所有素数的方法,具体步骤是:找到区间内(例如1~n)除2外所有2的倍数,划掉。然后找到除3外所有3的倍数,划掉。然后5,7,...,续行此法,直到√n为止。这个方法为什么是正确的呢?这样考虑,区间内所有素数都不可能包含其他素因子,因此可以将所有包含素因子的数从区间中去掉,另外没有划掉的最接近上一次划掉的数的倍数的那个数一定是素数(比如2的倍数4,6,8,10, ...被划掉后,最接近2的没有被划掉的数3一定是素数),由数论中的算术基本定理容易推出,如果所有比当前数小的素数都不是该数的因子,可以得到该数就是素数。另外,为何筛选到√n就可以停止了呢,因为过了√n后,某个数如果能被筛掉,那么它在以前一定被筛掉过了,这是因为n有素因子大于√n,那么它一定有一个小于√n的因数,该数要么是素数,要么是合数,是素数的话肯定在以前筛掉了,是合数的话它一定能分解成若干个素数的乘积,因此也在之前就被筛掉了。理论有了,马上实践它,下面的题目取自HDU_1215:
    Problem Description
    七夕节那天,月老来到数字王国,他在城门上贴了一张告示,并且和数字王国的人们说:"你们想知道你们的另一半是谁吗?那就按照告示上的方法去找吧!" 人们纷纷来到告示前,都想知道谁才是自己的另一半.告示如下:
    数字N的因子就是所有比N小又能被N整除的所有正整数,如12的因子有1,2,3,4,6. 你想知道你的另一半吗?
    Input
    输入数据的第一行是一个数字T(1<=T<=500000),它表明测试数据的组数.然后是T组测试数据,每组测试数据只有一个数字N(1<=N<=500000).
    Output
    对于每组测试数据,请输出一个代表输入数据N的另一半的编号.
    Sample Input
    3
    2
    10
    20
    Sample Output
    1
    8
    22
     
    容易想到的方法是暴力搜索:
    
    #include<stdio.h> // 超时
    int main(void)
    {
        int t, n, sum;
        scanf("%d", &t);
        while(t--)
        {
            scanf("%d", &n);
            sum = 0;
            for(int fac = 1; fac <= n/2; fac++) // n的因子不可能大于n/2
            {
                if(n % fac == 0)
                    sum += fac;
            }
            printf("%d
    ", sum);    
        }    
        return 0;
    }

    正如注释中写到的,该方法超时了,因此我们需要另觅良方来提高效率:可以枚举到√n,如果n有一个比它小的因子,那么必然有一个比它大的因子。

    
    #include<stdio.h> // 优化
    #include<math.h>
    int main(void)
    {
        int t, n, sum;
        scanf("%d", &t);
        while(t--)
        {
            scanf("%d", &n);
            sum = 1;
            // 写法一:for(int fac = 2; fac <= floor(sqrt(n)+0.5); fac++)直接超时,因为每次循环都得求一次sqrt.
            // 写法二:int bound = floor(sqrt(n)+0.5);
            //            for(int fac = 2; fac <= bound; fac++) // 452MS.
            // 写法三://280MS.对比可知,sqrt函数太耗时了.可以用乘法代替的话还是拒绝sqrt吧.
            for(int fac = 2; fac*fac <= n; fac++)
            {
                if(n % fac == 0)
                {
                    sum += fac;
                    if(fac*fac != n)
                        sum += n/fac;
                }
            }
            printf("%d
    ", sum);    
        }    
        return 0;
    }

    嗯?素数筛呢?没用吗?没用我介绍它干嘛:-,我们来试一下:

    
    #include<stdio.h> // 素数筛,405MS
    #include<math.h>
    #include<string.h>
    #define MAXN 500000
    int prime[MAXN+10];
    int main(void)
    {
        memset(prime, 0, sizeof(prime));
        // 构造1-500000所有素数,0代表是素数,1代表不是素数
        for(int i = 2; i*i <= MAXN; i++)
            if(prime[i] == 0)
                for(int j = i+i; j <= MAXN; j += i)
                    prime[j] = 1;
            
        int t, n, sum;
        scanf("%d", &t);
        while(t--)
        {
            scanf("%d", &n);
            sum = 1;
            if(prime[n] == 0)
                ;
            else
            {
                for(int fac = 2; fac*fac <= n; fac++)
                {
                    if(n % fac == 0)
                    {
                        sum += fac; 
                        if(fac*fac != n)
                            sum += n/fac;
                    }
                }
            }
            
            printf("%d
    ", sum);    
        }
        
        return 0;
    }

    可以看到,对比上一个程序,耗时有增无减,我只能找测试强度不够这茬了:-
    在仔细思考的话,这题实际是素数筛法的变形,素数筛法是留下素数,而我们这题要求找因子的和,测试够强的话,借助素数筛法确实可以帮助我们提高程序的效率,不过这还只是间接地利用素数筛法的思想,我们可以直接应用其思想,直指目标。也就是直接求每个数的因子和放到数组中:

    
    #include<stdio.h> // 根据素数筛的思想,来一个狠点的,78MS就AC
    #include<string.h>
    #define MAXN 500000
    int sum_fac[MAXN+10];
    int main(void)
    {
        for(int i = 2; i <= MAXN/2; i++) // MAXN的因子不可能超过MAXN/2
            for(int j = i+i; j <= MAXN; j+=i)
            {
                sum_fac[j] += i; // 将i换做1,便可以求因子个数
            }
        int t, n;
        scanf("%d", &t);
        while(t--)
        {
            scanf("%d", &n);
            printf("%d
    ", sum_fac[n]+1);
        }
        return 0;
    }

    最后,我们不讨论这个问题存不存在三角恋,自恋的问题,还是拿测试来说事吧,我测试了一下,求1的因子和时,按题意应该是0,不过测试数据中好像没有1,因为稍微修改一下程序,将1当作特例来处理,似乎任何值都可以过。

    All Rights Reserved.
    Author:海峰:)
    Copyright © xp_jiang. 
    转载请标明出处:http://www.cnblogs.com/xpjiang/p/4423016.html
    以上.
  • 相关阅读:
    记一次主从同步出现错误
    Mycat的学习
    MHA高可用集群
    MySQL进行 行累计
    设定从某个时间执行脚本,直到现在
    跟踪数据
    爬虫笔记
    爬虫练习
    css3-文本阴影
    vue生命周期
  • 原文地址:https://www.cnblogs.com/xpjiang/p/4423016.html
Copyright © 2011-2022 走看看