模板:判断素数+解释

(胡乱写个题吧,不要把他当做正常题来看,就是用来练习模板的…)
背景:上一回咱们说到的勇者,虽然在零件厂打起了功,但是隔三差五的也会到大草原上刷怪物。然而由于魔王已经被消灭,怪物们也不在活跃,为了保全自己,他们开始不安套路出牌——怎么个不按套路呢?他们将自己的生命值隐藏了起来,以为这样就可以为自己续命,但是由于这个大陆的神秘自然规律,他们的体力仍然能够通过某些提示来求出来(不然人类早就灭光了好不…)
问题:勇者所面对的怪物都含有一个数值n,勇者可以通过计算1~n内所有的素数的和来求出怪物的体力从而打倒怪物。然而为了减少勇者所受的体力,勇者希望能够尽可能少的时间快速打倒怪物。
输入:
一个整数n
输出:
怪物的体力m

勇者很快的写好了一段程序

#include<cstdio>
#include<cmath>
#include<algorithm>
#include<cstring>
#include<iostream>
using namespace std;
int main(){
    int n,sum=0;
    scanf("%d",&n);
    for(int i=2;i<=n;i++){
        int ok=0;
        for(int j=2;j<i;j++){
            if(i%j==0)ok=1;
        }
        if(ok==0)sum+=i;
    }
    printf("%d",sum);
    return 0;
}

(我想不用解释也能看懂吧…)
然而当勇者与怪物战斗的时候,发现有的怪物的体力太过于大了,以至于算起来十分的慢,于是他召唤出了自己的宠物精灵——路由器出来解决这个问题。
路由器把上述的代码改了改

#include<cstdio>
#include<cmath>
#include<algorithm>
#include<cstring>
#include<iostream>
using namespace std;
int main(){
    int n,sum=0;
    scanf("%d",&n);
    for(int i=2;i<=n;i++){
        int ok=0;
        for(int j=2;j<=sqrt(i);j++){
            if(i%j==0)ok=1;
        }
        if(ok==0)sum+=i;
    }
    printf("%d",sum);
    return 0;
}

(也不用说吧…)
当勇者乐呵呵的去打怪物的时候。
【野生的史莱姆王出现了!】
勇者大为一惊,马上掏出程序开始计算。
“快点啊!快点啊!”然而在勇者看到程序的结果之前……
【提示:您的体力值为0,已经自动回城】
没办法,他们为了得到更好的算法,来到了有名的魔法照相馆。
顾名思义,这个魔法照相馆的功能……就是照相!不过,这家店的主人喜欢收藏魔法书集,一看到勇者来了,二话没说丢给了他一本书。
上面记载了一个神奇的算法:Eratosthenes筛法

#include<cstdio>
#include<cmath>
#include<algorithm>
#include<cstring>
#include<iostream>
using namespace std;
int sum=0,he[100000000]={0};
void Eratosthenes(int n){
    for(int i=2;i<=n;i++){
        if(he[i]!=0)continue;
        sum+=i;
        //printf("%d\n",i);
        for(int j=2;j*i<=n;j++)he[j*i]=1;
    }
    printf("%d",sum);//输出素数和 
}
int main(){
    int n;
    scanf("%d",&n);
    Eratosthenes(n);
    return 0;
}

“这个魔法的精髓在于:他减少了判断素数的次数:只要我们能知道第一个素数是多少,那我们将这个素数*2,*3……一直到数足够大为止,刚刚我们的出来的数便全是合数,那么将他们全部标记,再往下找没有标记的数,再重复这些步骤,这个魔法的速率自然为
o(nloglogn)”
“不过,还有一种神奇的算法,用来解决一些高等魔物用的,算法是……”
后面看不清了。
不过勇者已经知足了,他将代码copy了一遍,然后出去了不超过几秒钟,就回来了,顺便拿到了史莱姆王的悬赏金。
————————分割线————————
这天,勇者还在百无聊赖的刷怪。
然而,他看到了一个巨大无比的怪物向他扑过来。
一看到这个怪物的体型如此庞大,勇者立马把所有的数据类型改成了longlong,准备迎战……
……纳尼?怎么怎么慢!
【提示:您的体力值为0,已经自动回城】
没办法,他们只好继续找更优秀的魔法。
别说,踏破铁鞋无觅处,在胡小兔dalao的博客下知道了那尘封多年的算法:
欧拉算法。

#include<cstdio>
#include<cmath>
#include<algorithm>
#include<cstring>
#include<iostream>
using namespace std;
int sum=0,su[100000000]={0},he[100000000]={0};
void Euler(int n){
    int cnt=0;
    for(int i=2;i<=n;i++){
        if(he[i]==0){
            cnt++;
            su[cnt]=i;  
            sum+=i;
        }
        for(int j=1;j<=cnt&&i*su[j]<=n;j++){
            he[su[j]*i]=1;
            if(i%su[j]==0)break;
        }
    }
    printf("%d",sum);//输出素数和 
}
int main(){
    int n;
    scanf("%d",&n);
    Euler(n);
    return 0;
}

勇者还没等看解释,就将代码copy了一遍,然后出去了。短短几秒,又一次斩获赏金。
后来,勇者和路由器一合计,决定自己在讲一遍这个魔法。
“首先,我们还是找到第一个素数,不过我们将他存起来,然后呢我们让当前所在的数字与我们已经筛过素数相乘,所得的数则全是合数,同时注意if(i%su[j]==0)break;然后继续前面步骤直到筛完,由于是线性的,并且每个数字都只被筛了一遍,所以效率为o(n)”
“至于那个特判怎么解释,详细证明请使用魔法网络找高级魔法师来讲解或者是刚说道的dalao的博客下面看一看,这里给出自己的解释”
“首先,为什么这个魔法高效呢,核心就在于每个数只筛选一遍,这就是那个特判的目的,试想一下,如果我们当前的数是一个质数,那么他和所有的质数都乘一遍,所得出的结果不可能会在筛到第二次。”
“但如果是合数k的话,他一定含有一个我们已经筛出来的质数因子,我们找到其中最小的a,然后b=k/a。”
“这时我们就会惊奇的发现,这个合数已经在当前值为b的时候被筛掉了,因此后面在继续筛的话反而浪费时间。“
“(因为总会筛掉的,例如设m为当前合数=2(最小质因数)*b,那么我们用这个式子求出3m为合数的话就是3m=2*3*b=2(3*b),即我们在当前值为3*b的时候筛掉这个合数)(当然,为了保证能够筛掉这样的数,我们就要先筛后特判)”
————举例子时间————
“举个例子吧,比如当前值是9,我们发现9能被3整除,于是我们得出了18(当前值为9,质数因子为2)与27(当前值为9,质数因子为3)是合数,在之后的18能被2整除,于是我们得出了36(当前值为18,质数因子为2)是合数……”
“而如果我们得出了6为合数,能被2整除,如果我们用6*3=18得出18为合数的话不就与2*9重复了吗,所以我们只采用最后的那种方法来筛,就能只筛一次了”
“再举个例子,证明56是合数的话,我们发现56=2*2*2*7,这样我们只用等到当前值为28的时候便能筛掉这个合数,而如果我们不这样做的话,他就会……额,情况太多了,请看官自己推吧”
————举例子完毕————
“怎么样,是否明白了那?”

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值