代码改变世界

素数筛选法

2015-04-13 21:22  星星之火✨🔥  阅读(1046)  评论(0编辑  收藏  举报
素数筛选法是一种快速求某个大整数区间内的所有素数的方法,具体步骤是:找到区间内(例如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\n", 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\n", 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\n", 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\n", 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
以上.