1. 程式人生 > 其它 >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幾分鐘的事情