其他分享
首页 > 其他分享> > 09. 素数筛&质因数分解

09. 素数筛&质因数分解

作者:互联网

目录

09. 素数筛&质因数分解

素数筛法

素数筛其实就是 判断 1~N 中有哪些是素数。

素数就是指大于 1的数中,因子只有 1和它本身的数。

换句话说就是素数 N 在区间 [2, N-1] 一定不存在因子。

那么傻瓜式判断,时间复杂度 O(n):

bool isprime(int n){
    if(n<2) return 0;
    for(int i=2; i<n; i++){
        if(n%i==0) return 0; // i是 n的因子
    }
    return 1;
}

其实对于这个我们可以适当优化,思路如下:
令 :\(a <= b, a*b = n;\)
则 :\(a*a < a*b = n;\)
即 :\(a <= sqrt(n);\)

这样就将因数的范围由 [2, n-1] 缩减到了 [2, sqrt(n)], 时间复杂度 O(sqrt(n)):

bool isprime(int n){// 写法1
    if(n<2) return 0;
    for(int i=2; i<=sqrt(n); i++)
        if(n%i==0) return 0;
    return 1;
}
bool isprime(int n){// 写法2
    if(n<2) return 0;
    for(int i=2; i*i<=n; i++)
        if(n%i==0) return 0;
    return 1;
}

在上述求素数的方法中我们所使用的方法是对每一个数进行素数判断,
如果这个数没有除1和它本身外的其它因数,那么该数就是因数。

但是这样的算法时间复杂度是线性的,能不能优化一下呢?
肯定是可以的,首先我们发现如下规律:

2是素数,那么2*2=4不是素数,2*3=6不是素数,2*4=8不是素数...
3是素数,那么3*2=6不是素数,3*3=9不是素数,3*4=12不是素数...

于是想到素数的倍数(素数本身不计算在内)是合数,那么就没必要判断了呀,可以直接筛掉 - 埃式筛法

比如我想求 [1, 10] 的素数,有:

1 2 3 4 5 6 7 8 9 10 // 原数据
  2 3 4 5 6 7 8 9 10 // 1不是素数,也不是合数,筛除 1
  2 3 4 5 6 7 8 9 10 // 2是素数,筛除 2的倍数
  2 3   5   7   9    // 3是素数,筛除 3的倍数
  2 3   5   7        // 4是合数,continue
  2 3   5   7        // 5是素数,筛除 5的倍数
  2 3   5   7        // 6是合数,continue
  2 3   5   7        // 7是素数,筛除 7的倍数
  2 3   5   7        // 8是合数,continue
  2 3   5   7        // 9是合数,continue
  2 3   5   7        // 10是合数,continue

埃式筛法 - 埃拉托斯特尼筛法
原理:筛除倍数,素数的倍数一定不是素数,时间复杂度 O(nlogn)

bool isprimes[N];   //判断是否素数, 1是素数,0不是素数
void Eratosthenes(int maxn){
    //将isprime全部初始化为 1,相当于设置全部的 i都为质数
    memset(isprimes, 1, sizeof(isprimes));
    isprimes[0]=isprimes[1]=0;       //0,1不是质数
    for(int i=2; i<=maxn; i++){
        if(isprimes[i]==0) continue; //退出当前循环,继续下次循环
        for(int j=2; i*j<=maxn; j++){
            isprimes[i*j]=0;         // i*j不是质数
        }
    }
}

对于埃式筛法,发现会对同一个数多次筛除,这没必要啊,于是就有了 - 线性筛法

线性筛法 - 欧拉筛法,时间复杂度 O(n)
原理:素数的倍数是合数,且合数具有唯一最小素因子。

bool isprimes[N];   //判断是否素数, 1是素数,0不是素数
int primes[N] pn=0; //primes[i]为第 i个素数, pn 为素数个数
void FastSieve(int maxn){
// 初始化isprime全部为1,即初始化所有i为质数
    memset(isprimes, 1, sizeof(isprimes));//0,1不是质数
    isprimes[0] = isprimes[1] = pn = 0;
    for(int i=2; i<=maxn; i++){
        if(isprimes[i]) primes[++pn] = i; //将质数 i存入primes
        for(int j=1; j<=pn; j++){
            if(i*primes[j] > maxn) break;
            isprimes[i*primes[j]] = 0;    //质数的倍数都是合数
            if(i%primes[j]==0) break;     //利用最小素因子筛素数关键
        }
    }
}
#include<iostream>
#include<cstring>
using namespace std;
const int N=1e6+1;
bool isprimes[N];   //判断是否素数, 1是素数,0不是素数
int primes[N] pn=0; //primes[i]为第 i个素数, pn 为素数个数
//朴素方法(时间复杂度 O(sqrt(n))
bool isPrimes(int n){
    if(n<2) return false;
    for(int i=2; i*i<=n; i++){
        if(n%i==0) return false;
    }
    return true;
}
//埃式筛法 - 埃拉托斯特尼筛法
//原理:筛除倍数,一个素数的倍数一定不是素数,时间复杂度 O(nlogn)
void Eratosthenes(int maxn){
    //将isprime全部初始化为 1,相当于设置全部的 i都为质数
    memset(isprimes, 1, sizeof(isprimes));
    isprimes[0]=isprimes[1]=pn=0;    //0,1不是质数
    for(int i=2; i<=maxn; i++){
        if(isprimes[i]==0) continue; //退出当前循环,继续下次循环
        primes[++pn] = i;            //将素数 i存入数组 primes
        for(int j=2; i*j<=maxn; j++){
            isprimes[i*j]=0;         // i*j不是质数
        }
    }
}
//线性筛法 - 欧拉筛法,时间复杂度 O(n)
//原理:素数的倍数是合数,且合数具有唯一最小素因子
void FastSieve(int maxn){
// 初始化isprime全部为1,即初始化所有i为质数
    memset(isprimes, 1, sizeof(isprimes));//0,1不是质数
    isprimes[0] = isprimes[1] = pn = 0;
    for(int i=2; i<=maxn; i++){
        if(isprimes[i]) primes[++pn] = i;//将质数 i存入primes
        for(int j=1; j<=pn; j++){
            if(i*primes[j] > maxn) break;
            isprimes[i*primes[j]] = 0;   //质数的倍数都是合数
            if(i%primes[j]==0) break;    //利用最小素因子筛素数关键
        }
    }
}
int main() {
    int n; cin>>n;
    Eratosthenes(n);
//    FastSieve(n);
    //输出pn个素数
    for(int i=1; i<=pn; i++) cout<<primes[i]<<" "; cout<<endl;
    return 0;
}

质因数分解

【题目描述】对于正整数N的质因数分解,指的是将其写成:\(N=p1*p2*…*pm\);
其中,p1,p2,…,pm 为不下降的质数。给定N,输出其质因数分解的形式。

输入格式:1个正整数N。
输出格式:N的质因数分解的形式 \(N=p1*p2*…*pm\),其中p1,p2,…,pm都是质数而且p1<=p2<=…<=pm。
输入样例:60
输出样例:60=2*2*3*5

bool isprimes(int m) {
    for(int i=2; i*i<=m; i++)
        if(m%i==0)return 0;
    return 1;
}
for(int i=2; i<=n; i++){
    if(isprimes(i)&&n%i==0) {
        while(n%i==0) {
            tot++;
            if(tot==1) printf("%d=%d",n,i)
            else printf("*%d",i);
            n/=i;
        }
    }
}

这个算法跑的非常慢,无法满足N较大时的需求。
我们发现对于所有数字我们都判断过i是否是一个质数,而事实上我们并不需要。

因为加入n%i==0,那么i必然是一个质数!证明如下:
假设i不是质数,i必存在一个质数因子j,那么j<i,但之前已经枚举过了j,
n已经把质数j全部分解出去了,当前的n已经没有了j因子n%j!=0,与n%i==0矛盾!
所以当n%i==0时,i必然为一个质数。
int main(){
    int n,total=0; cin>>n;
    for(int i=2; i*i<=n; i++){
        while(n%i==0){
            total++;
            if(total==1) cout<<n<<"="<<i;
            else cout<<"*"<<i;
            n /= i;
        }
    }
    return 0;
}

P1075 [NOIP2012 普及组] 质因数分解

【题目描述】已知正整数n是两个不同的质数的乘积,试求出两者中较大的那个质数。

输入格式:一个正整数n。
输出格式:一个正整数pp,即较大的那个质数。
输入样例:21
输出样例:7
数据范围:n≤2×10^9

题解:

#include<iostream>
using namespace std;
bool isPrimes(int n){//判断n是不是素数
    if(n<2) return false;
    for(int i=2; i*i<=n; i++)
        if(n%i==0) return false;
    return true;
}
int main_60(){
    int n;cin>>n;
    for(int i=n-1; i>=2; i--){
        if(isPrimes(i) && isPrimes(n/i) && n%i==0){
            cout<<i;break;//return 0;
        }
    }
    return 0;
}
int main_100() {
    int n;    cin>>n;
// 唯一分解定理:一个数能且只能分解为一组质数的乘积
    for(int i=2; i<=n; i++) {
        if(n%i==0) {    //最小的质因数
            cout<<n/i;  //输出最大的质因数
            break;
        }
    }
    return 0;
}

P1029 [NOIP2001 普及组] 最大公约数和最小公倍数问题

【题目描述】输入两个正整数 x0,y0,求出满足下列条件的 P, Q 的个数:

  1. P,Q 是正整数。
  2. 要求 P, Q以 x0 为最大公约数,以 y0 为最小公倍数。

试求:满足条件的所有可能的 P, Q 的个数。

输入格式:一行两个正整数x0,y0
输出格式:一行一个数,表示求出满足条件的 P,Q 的个数
输入样例:3 60
输出样例:4

题解:

#include<bits/stdc++.h>
using namespace std;
int gcd(int a, int b){//最大公约数
    if(b==0) return a;
    else return gcd(b, a%b);
}
int lcm(int a, int b){//最小公倍数
//定理:两个数的乘积等于它们最大公约数和它们最小公倍数的积。
    return a*b/gcd(a,b);
}

int main(){
    int x, y, count=0; cin>>x>>y;
    for(int p=1; p<=max(x,y); p++){//对p进行枚举
//定理:两个数的乘积等于它们最大公约数和它们最小公倍数的积。
//因为 x,y是 p,q的最大公约数和最小公倍数
//所以 p*q = x*y
//由于 x,y已知,p是枚举的,所以可以计算 q=x*y/p;
        int q=x*y/p; //根据枚举的p的值,计算对应的q值
        //按照最大公约数最小公倍数判断结果是否正确
        if(x==gcd(p, q) && y==p*q/gcd(p,q)){
            count++;
        }
    }
    cout<<count;return 0;
}

P3383 【模板】线性筛素数

【题目描述】给定一个范围 n,有 q 个询问,每次输出第 k 小的素数。
提示:如果你使用 cin 来读入,建议使用 std::ios::sync_with_stdio(0) 来加速。

输入格式:
第一行包含两个正整数 n,q,分别表示查询的范围和查询的个数。
接下来 q 行每行一个正整数 k,表示查询第 k 小的素数。
输出格式:输出 q 行,每行一个正整数表示答案。
输入样例:

100 5
1
2
3
4
5

输出样例:

2
3
5
7
11

数据范围:对于 100% 的数据,n = 10^8, 1≤q≤10^6,保证查询的素数不大于 n。
题解:

#include<bits/stdc++.h>
using namespace std;
const int N=1e8+1;
int a[N];
bool isprimes[N];
int primes[N], pn=0;
bool isPrimes(int num){
    if(num<2) return 0;
    for(int i=2; i*i<=num; i++){
        if(num%i==0) return 0;
    return 1;
}

//埃式筛法 - 埃拉托斯特尼筛法
//筛除倍数,一个素数的倍数一定不是素数
void Eratosthenes(int maxn){
// 初始化isprime全部为1,即初始化所有i为质数
    memset(isprimes, 1, sizeof(isprimes));
    isprimes[0] = isprimes[1] = pn = 0;//0,1不是质数
    for(int i=2; i<=maxn; i++){
        if(isprimes[i]==0) continue;//退出当前循环,继续下次循环
        primes[++pn] = i;//将质数 i存入primes
        for(int j=2; i*j<=maxn; j++){
            isprimes[i*j] = 0;//质数的倍数都是合数
        }
    }
}
//线性筛法 - 欧拉筛法
void FastSieve(int maxn){
// 初始化isprime全部为1,即初始化所有i为质数
    memset(isprimes, 1, sizeof(isprimes));//0,1不是质数
    isprimes[0] = isprimes[1] = pn = 0;
    for(int i=2; i<=maxn; i++){
        if(isprimes[i]) primes[++pn] = i;//将质数 i存入primes
        for(int j=1; j<=pn; j++){
            if(i*primes[j] > maxn) break;
            isprimes[i*primes[j]] = 0;//质数的倍数都是合数
            if(i%primes[j]==0) break; //利用最小素因子筛素数关键
        }
    }
}
int main(){
    int n=100, q=10;  scanf("%d%d", &n, &q);
    for(int i=1; i<=q; i++) scanf("%d", &a[i]);
//    Eratosthenes(n);//埃氏筛法
    FastSieve(n);//线性筛法
    for(int i=1; i<=q; i++){
        printf("%d\n",primes[a[i]]);
    }
    return 0;
}

P1218 [USACO1.5]特殊的质数肋骨 Superprime Rib

【题目描述】农民约翰的母牛总是产生最好的肋骨。你能通过农民约翰和美国农业部标记在每根肋骨上的数字认出它们。农民约翰确定他卖给买方的是真正的质数肋骨,是因为从右边开始切下肋骨,每次还剩下的肋骨上的数字都组成一个质数。
举例来说:7 3 3 1 全部肋骨上的数字 7331 是质数;三根肋骨 733 是质数;二根肋骨 73 是质数;当然,最后一根肋骨 7 也是质数。7331被叫做长度 4 的特殊质数。
写一个程序对给定的肋骨的数目 n,求出所有的特殊质数。1 不是质数。

输入格式:一行一个正整数 n。
输出格式:按顺序输出长度为 n 的特殊质数,每行一个。
输入样例:4
输出样例:

2333
2339
2393
2399
2939
3119
3137
3733
3739
3793
3797
5939
7193
7331
7333
7393

数据范围:对于 100% 的数据,1≤n≤8。
题解:

#include<bits/stdc++.h>
using namespace std;

const int N=1e8+1;
int a[N];
bool isprimes[N];
int primes[N], pn=0;

bool isPrimes(int num){
    if(num<2) return 0;
    for(int i=2; i*i<=num; i++)
        if(num%i==0) return 0;
    return 1;
}

//埃式筛法 - 埃拉托斯特尼筛法
//筛除倍数,一个素数的倍数一定不是素数
void Eratosthenes(int maxn){
// 初始化isprime全部为1,即初始化所有i为质数
    memset(isprimes, 1, sizeof(isprimes));
    isprimes[0] = isprimes[1] = pn = 0;//0,1不是质数
    for(int i=2; i<=maxn; i++){
        if(isprimes[i]==0) continue;//退出当前循环,继续下次循环
        primes[++pn] = i;           //将质数 i存入primes
        for(int j=2; i*j<=maxn; j++){
            isprimes[i*j] = 0;      //质数的倍数都是合数
        }
    }
}

//线性筛法 - 欧拉筛法
void FastSieve(int maxn){
// 初始化isprime全部为1,即初始化所有i为质数
    memset(isprimes, 1, sizeof(isprimes));//0,1不是质数
    isprimes[0] = isprimes[1] = pn = 0;
    for(int i=2; i<=maxn; i++){
        if(isprimes[i]) primes[++pn] = i;//将质数 i存入primes
        for(int j=1; j<=pn; j++){
            if(i*primes[j] > maxn) break;
            isprimes[i*primes[j]] = 0;   //质数的倍数都是合数
            if(i%primes[j]==0) break;    //利用最小素因子筛素数关键
        }
    }
}
int main(){
//    freopen("test.out", "w", stdout);
    int n=4; cin>>n;
    if(n == 8) {//这一点确实有点难,只能使用打表或者二分思想来解决
        cout<<"23399339"<<endl
          <<"29399999"<<endl
          <<"37337999"<<endl
          <<"59393339"<<endl
          <<"73939133"<<endl;return 0;
    }
    n = pow(10,n);

//    Eratosthenes(n);//埃氏筛法
    FastSieve(n);//线性筛法
    for(int i=n/10; i<=n-1; i++){
        int temp=i;
        while(temp){
            if(isprime[temp]==0) break;
            temp /= 10;
        }
        if(temp==0) cout<<i<<endl;
    }

/*  for(int i=1; i<=n; i++){
        if(primes[i] > n-1) break;
        if(n/10 <= primes[i]){
            int temp = primes[i];
            while(temp){
                if(isprimes[temp]==0) break;
                temp /= 10;
            }
            if(temp==0) cout<<primes[i]<<endl;
        }
    }*/
    return 0;
}

上面这个是用筛法程序做的,那么我们还是讲一讲取巧方法 - 打表
所谓打表,就是写一个最简单的程序,通过该程序得到部分中间数据,甚至是最终答案,从而达到能简化运算次数的目的。

#include<iostream>
#include<cmath>
using namespace std;
bool isPrimes(int n){
    if(n<2) return 0;
    for(int i=2; i*i<=n; i++)
        if(n%i==0) return 0;
    return 1;
}
void pd() {//按要求打表 - 用朴素算法
    int n; cin>>n;
    n=pow(10,n);
    for(int i=2; i<=n; i++){
        if(isPrimes(i)){
            int temp=i;
            while(temp){
                if(isPrimes(temp)==0) break;
                temp /= 10;
            }
            if(temp==0) cout<<i<<",";
        }
    }
}

const int N=10;
int primes[N][20]={//按要求对素数(1~8位)打表
{},
{2,3,5,7},
{23,29,31,37,53,59,71,73,79},
{233,239,293,311,313,317,373,379,593,599,719,733,739,797},
{2333,2339,2393,2399,2939,3119,3137,3733,3739,3793,3797,5939,7193,7331,7333,7393},
{23333,23339,23399,23993,29399,31193,31379,37337,37339,37397,59393,59399,71933,73331,73939},
{233993,239933,293999,373379,373393,593933,593993,719333,739391,739393,739397,739399},
{2339933,2399333,2939999,3733799,5939333,7393913,7393931,7393933},
{23399339,29399999,37337999,59393339,73939133}};

int main() {
    int n; cin>>n;
    for(int i=0; primes[n][i]!=0; i++){
        cout<<primes[n][i]<<endl;
    }return 0;
}//相对线性筛法而言,这道题我更推荐使用打表的方法来解决,不过也就10几分钟的事情

image

标签:因数分解,int,质数,09,isprimes,素数,primes,合数
来源: https://www.cnblogs.com/hellohebin/p/16323239.html