2020年2月14日 林大OJ习题 素数筛

2020年2月14日 素数筛

素数筛其实是一种打表的方法,用更小的时间复杂度打出来素数表,达到节省时间的目的,缺点是空间复杂度较大。刚开始做题就发现了这类题目占用的内存比之前的大的多,甚至有MLE的风险。。
先来写一下素数筛的原理与模板:
原理来源:素数筛法

整数的唯一分解定理:

任何一个大于1的自然数 N,如果N不为质数,都可以唯一分解成有限个质数的乘积N=P1 ^ a1 · P2 ^ a2 · P3 ^ a3 · … · Pn ^ an ,这里P1<P2<P3<…<Pn均为质数,其诸指数 ai 是正整数。
(:当然质数的话直接就是质数本身)

一、埃氏筛法 空间复杂度:O(nloglogn)
埃氏筛法的思想:

枚举每个素数,然后把他们的倍数都打上标记,从而达到筛出的目的
质数的倍数一定不是质数
埃氏筛的模板:

const int N=1e7;//埃氏筛
int n,cnt,prime[N];
bool b[N];
void get_prime()
{
    memset(b,1,sizeof(b));
    b[1]=0;
    for(int i=2;i<=N;i++){
        if(b[i]){
            prime[++cnt]=i;
            for(int j=2;j*i<=N;j++)
                b[i*j]=0;
        }
    }
}
埃氏筛的适用范围大约是1e6~1e7,1e7左右就有TLE的风险。

而且埃氏筛法还有一个缺陷 :
对于一个合数,有可能被筛多次。例如 30 = 2 * 15 = 3 * 10 = 5*6……
那么如何确保每个合数只被筛选一次呢?我们只要用它的最小质因子来筛选即可,这便是欧拉筛法。

二、欧拉筛法(线性筛法) 空间复杂度几乎为O(n)

基本思想:在埃氏筛法的基础上,让每个合数只被它的最小质因子筛选一次,以达到不重复的目的,每个数都只筛一次,相当于直接判断。

const int N=4e7;
int n,cnt,prime[N+10];
bool b[N+10];
void get_prime(int n)
{
    memset(b,1,sizeof(b));
    b[0]=b[1]=0;
    for(int i=2;i<=n;i++){
        if(b[i])
            prime[++cnt]=i;
        for(int j=1;j<=cnt && i*prime[j]<=n;j++){
            b[i*prime[j]]=0;
            if(i%prime[j]==0) break;
        }
    }
}

看一下今天的题目,经过jwGG的数据加强,感觉今天的题目很有挑战性,即使强如大佬,今天也没有AK。

林大OJ 585 最大素因子

如果要用memset函数把数组初始化为1,则不能开int数组,而要用bool数组。而且如果开int数组可能会MLE。
#include <bits/stdc++.h>

using namespace std;

const int N=1e6;
int n,cnt,prime[N],a[N],b[N];
void get_prime()
{
    cnt=1;
    memset(b,1,sizeof(b));
    b[0]=b[1]=0;
    for(int i=2;i<=N;i++){
        if(b[i]) prime[cnt++]=i;
        for(int j=1;j<=cnt && prime[j]*i<=N;j++){
            b[prime[j]*i]=0;
            if(i%prime[j]==0) break;//保证每个合数只会被它的最小质因数筛去,因此每个数只会被标记一次
        }
    }
    for(int i=1;i<=N;i++){
        if(b[i]) a[i]=a[i-1]+1;
        else a[i]=a[i-1];
    }
}

int main()
{
    ios::sync_with_stdio(false);
    get_prime();
    while(cin>>n){
        if(n==1){
            cout<<"0"<<endl;
            continue;
        }
        for(int i=n;i>=2;i--){//注意是i>=2,而不是i>=sqrt(n)或i>=n/2
            if(n%i==0 && b[i]){
                cout<<a[i]<<endl;
                break;
            }
        }
    }
    return 0;
}

林大OJ 586 纯素数

这道题的难点在于如何把最高位去掉。解决方法是反着来:先取各位上的数,然后从1位数开始倒着加回来。

#include <bits/stdc++.h>

using namespace std;

const int N=1e6;
int n,cnt,prime[N+10];
bool b[N+10];
void get_prime()
{
    memset(b,1,sizeof(b));
    b[1]=0;
    for(int i=2;i<=N;i++){
        if(b[i]) prime[++cnt]=i;
        for(int j=1;j<=cnt && i*prime[j]<=N;j++){
            b[i*prime[j]]=0;
            if(i%prime[j]==0) break;
        }
    }
}
bool judge(int x)
{
    int a[8],len=0;
    memset(a,0,sizeof(a));
    while(x){
        a[++len]=x%10;
        x/=10;
    }
    for(int i=1;i<=len;i++){
        int sum=0;
        for(int j=i;j>=1;j--)
            sum=sum*10+a[j];
        if(!b[sum]) return 0;
    }
    return 1;
}

int main()
{
    ios::sync_with_stdio(false);
    get_prime();
    while(cin>>n){
        if(judge(n)) printf("YES\n");
        else printf("NO\n");
    }
    return 0;
}

林大OJ 587 半素数

让该数与其的因子同是在素数表内,则算作成功。

#include <bits/stdc++.h>

using namespace std;

const int N=1e6;
int n,cnt,prime[N+10];
bool b[N+10];
void get_prime()
{
    memset(b,1,sizeof(b));
    b[1]=0;
    for(int i=2;i<=N;i++){
        if(b[i]) prime[++cnt]=i;
        for(int j=1;j<=cnt && i*prime[j]<=N;j++){
            b[i*prime[j]]=0;
            if(i%prime[j]==0) break;
        }
    }
}
bool judge(int n)
{
    for(int i=2;i*i<=n;i++)
        if(n%i==0 && b[i] && b[n/i]) return 1;
    return 0;
}

int main()
{
    ios::sync_with_stdio(false);
    get_prime();
    while(cin>>n){
        if(judge(n)) printf("YES\n");
        else printf("NO\n");
    }
    return 0;
}

林大OJ 781 素数与数论

#include <bits/stdc++.h>

using namespace std;

const int N=1e6;
int n,cnt,prime[N+10],num[N+10];//num数组记录1~i有几个素数
bool b[N+10];
void get_prime()
{
    memset(b,1,sizeof(b));
    b[1]=0;
    for(int i=2;i<=N;i++){
        if(b[i]) prime[++cnt]=i;
        for(int j=1;j<=cnt && i*prime[j]<=N;j++){
            b[i*prime[j]]=0;
            if(i%prime[j]==0) break;
        }
        num[i]=cnt;
    }
}

int main()
{
    ios::sync_with_stdio(false);
    int a,b;
    get_prime();
    while(cin>>a>>b){
        printf("%d\n",num[b]-num[a-1]);//左端点也可能是素数
    }
    return 0;
}

林大OJ 825 函数版素数判定

判定素数的模版题。

#include <bits/stdc++.h>

using namespace std;

typedef long long LL;
const int N=1e7;
int prime[N+10];
bool b[N+10];
int cnt=1;
int init()
{
    memset(b,1,sizeof(b));
    b[0]=b[1]=0;
    for(int i=2;i<=N;i++){
        if(b[i])
            prime[cnt++]=i;
        for(int j=1;j<=cnt && prime[j]*i<=N;j++){
            b[prime[j]*i]=0;
            if(i%prime[j]==0) break;
        }
    }
    return 0;
}

int main()
{
    LL n;
    init();
    while(~scanf("%lld",&n)){
        if(b[n]) printf("YES\n");
        else printf("NO\n");
    }
    return 0;
}

林大OJ 2113 / 洛谷P3383 素数线性筛

线性筛(欧拉筛)的模版题。

#include <bits/stdc++.h>

using namespace std;

const int N=4e7;
int n,cnt,prime[N+10];
bool b[N+10];
void get_prime(int n)
{
    memset(b,1,sizeof(b));
    b[0]=b[1]=0;
    for(int i=2;i<=n;i++){
        if(b[i])
            prime[++cnt]=i;
        for(int j=1;j<=cnt && i*prime[j]<=n;j++){
            b[i*prime[j]]=0;
            if(i%prime[j]==0) break;
        }
    }
}

int main()
{
    int n,q;
    scanf("%d %d",&n,&q);
    get_prime(n);
    while(q--){
        int k;
        scanf("%d",&k);
        printf("%d\n",prime[k]);
    }
    return 0;
}

林大OJ 1704 知否知否,应是绿肥红瘦

#include <bits/stdc++.h>

using namespace std;

typedef long long LL;
const int N=1e7+1;
int prime[N],b[N];
int cnt=1;
int init()
{
    memset(b,1,sizeof(b));
    b[0]=b[1]=0;
    for(int i=2;i<N;i++){
        if(b[i])
            prime[cnt++]=i;
        for(int j=1;j<=cnt && prime[j]*i<=N;j++){
            b[prime[j]*i]=0;
            if(i%prime[j]==0) break;
        }
    }
    return 0;
}

int ss(LL n)
{
    int flag=0;
    for(int i=1;prime[i]<=sqrt(n*1.0);i++)
    if(n%prime[i]==0){
        flag=1;break;
    }
    if(n==1) flag=1;
    return flag;
}

int main()
{
    ios::sync_with_stdio(false);
    int n;
    LL x,y,z;
    init();
    cin>>n;
    while(n--){
        cin>>x>>y>>z;
        if(ss(x+y-z)==0) cout<<"yes"<<endl;
        else cout<<"no"<<endl;
    }
    return 0;
}

林大OJ 1262 五十弦翻塞外声


答案来自:大佬的题解。用试除法,不打素数表。

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
ll t,x;
ll get_ans()
{
    ll ans=1;
    for(ll i=2;i*i<=x;i++)
    {
        if(x%i==0)
        {
            ll tmp=1;
            while(x%i==0)//将x除以其因子(必是x的素因子)
            {
                tmp=tmp*i;
                x=x/i;
            }
            ans*=(tmp*i-1)/(i-1);
        }
    }
    if(x>1)ans*=(1+x);//注意,若x未被除尽到1,说明最后的x还剩下一个素数没有乘到答案里面,把它乘进去即可
    return ans;
}
int main()
{
    ios::sync_with_stdio(false);
    cin>>t;
    while(t--)
    {
        cin>>x;
        printf("%lld\n",get_ans());
    }
    return 0;
}

林大OJ 1321 差点是素数

终极压轴大题,答案待补全。。
希望有一天我能有热情解出这道题来。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值