1. 程式人生 > 其它 >演算法刷題-數論-質數的判定、分解質因數、篩質數

演算法刷題-數論-質數的判定、分解質因數、篩質數

技術標籤:演算法與資料結構

文章目錄

數論

1. 質數

質數:在大於1的整數中,如果只包含1和它本身這兩個約數,那麼這個數就稱為質數。

判斷質數最暴力的寫法,按照質數的定義:看是否有其他的因子。

最樸素的暴力的時間複雜度O(n)

//時間複雜度O(n)
bool isPrime(int n){
    if(n<2) return false;
    for(int i=2;i<n;i++)
        if(n%i==0) return false;
    return
true; }

質數的判定—試除法

題目連結:Acwing866. 試除法判定質數

優化:列舉到 n \sqrt{n} n ,因為因數都是成對出現的,
如果d是n的因子,那麼n/d也是n的因子,所以只需要列舉一部分就行,列舉哪一部分呢?就是d≤n/d 推出, d ≤ n d≤\sqrt{n} dn

這裡 n \sqrt{n} n 的寫法需要注意,這種呼叫數學函式 n \sqrt{n} n 的寫法不好,太慢;另外寫成 i ∗ i ≤ n i*i≤n iin存在溢位風險,最好的寫法是 i ≤ n / i i≤n/i in/i

試除法求質數的時間複雜度O( n \sqrt{n}

n )

//優化成O(根號n)
bool isPrime(int n){
    if(n<2) return false;
    for(int i=2;i<=n/i;i++)
        if(n%i==0) return false;
    return true;
    
}

分解質因數—試除法

題目連結:Acwing867. 分解質因數

暴力求質因數

下面i就是質因子,s是質因子i的階數。

暴力的時間複雜度O(n)

void divide(int n){
    
    for(int i=2;i<=n;i++){
        if(n%i==0){ //i一定是質數,因為下面n/=i,n一直在更新,如果是合數的話,已經被2除乾淨了
int s=0;//統計質因子的階數 while(n%i==0){ s++; n/=i; } cout<<i<<" "<<s<<endl; } } }

優化

給出一個重要性質:正整數n的因子中,最多包含1個大於 n \sqrt{n} n 的質因子。(可以用反證法來證明)

這裡試除法的優化就是列舉到 n \sqrt{n} n ,然後剩下的1個大於 n \sqrt{n} n 的質因子單獨處理,這樣試除法分解質因數的時間複雜度O( n \sqrt{n} n )

ac程式碼

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

//試除法分解質因數
void divide(int n){
    //處理≤sqrt(n)的質因子
    for(int i=2;i<=n/i;i++){
        if(n%i==0){
            int s=0;
            while(n%i==0){
                s++;
                n/=i;
            }
            cout<<i<<" "<<s<<endl;
        }
    }
    //單獨處理大於sqrt(n)的質因子
    if(n>1) cout<<n<<" "<<1<<endl;
    
}

int main(){
    int n;
    cin>>n;
    int tmp;
    for(int i=0;i<n;i++){
        cin>>tmp;
        divide(tmp);
        cout<<endl;
    }
    
}

篩質數

題目連結acwing868. 篩質數

樸素篩法

樸素篩法的思想:從小到大列舉所有數,每次刪掉該數的所有倍數,比如列舉到2,就刪掉所有2的倍數。列舉到3,就刪掉所有3的倍數。

需要用到prime陣列,用來存放所有的質數;st陣列,用來記錄是否是某個數的倍數,即判斷哪些是質數。需要count1變數,來儲存質數的個數。

遍歷的時候,需要考慮等號取不取。

樸素篩法的時間複雜度 O ( n × l o g n ) O(n\times logn) O(n×logn)


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

const int maxn=1000010;

int prime[maxn];//prime陣列存的是從小到大的質數
int count1=0; //質數的個數
bool st[maxn]; //判斷是否是質數


//樸素的篩法
void primeNumber(int n){
    for(int i=2;i<=n;i++){
        if(!st[i]){ //i不是別人的倍數,那麼就是質數
            prime[count1++]=i;
        }
        for(int j=i+i;j<=n;j+=i) st[j]=true;//倍數篩掉
    }
    cout<<count1<<endl;
    
    
    
}

int main(){
    int n;
    cin>>n;
    primeNumber(n);
    return 0;
    
}
埃氏篩法

優化:不需要篩掉所有數的倍數(合數的倍數一定不是質數,不用管),只需要篩掉質數的倍數,(因為質數的倍數是合數)。

//優化的篩法
void primeNumber1(int n){
    for(int i=2;i<=n;i++){
        if(!st[i]){ //i不是別人的倍數,那麼就是質數
            prime[count1++]=i;
            for(int j=i+i;j<=n;j+=i) st[j]=true;//質數的倍數篩掉
        }   
    }
    cout<<count1<<endl;
    
    
    
}

有質數定理:當n很大時,1~n中有 n l n n \frac{n}{lnn} lnnn個質數。

優化的篩法(埃氏篩法)時間複雜度 O ( n × l o g l o g n ) O(n\times loglogn) O(n×loglogn),可以粗略地看成是O(n)。

兩次提交結果

在這裡插入圖片描述

線性篩法

先說效率,數量級在10^7時候,線性篩法比埃氏篩法快一倍大概。
線性篩法時間複雜度O(n)

線性篩法的核心:一個數k只會被k的最小質因子篩掉。

//線性篩法

void primeNumber2(int n){
    for(int i=2;i<=n;i++){
        if(!st[i]) prime[count1++]=i;
        for(int j=0;prime[j]<=n/i;j++){
            st[prime[j]*i]=true;
            if(i %  prime[j]==0) break; //prime[j]一定是i的最小質因子
        }
    }
    cout<<count1<<endl;
}