1. 程式人生 > 實用技巧 >素數

素數

學數論 心情複雜 沒什麼好說的 , , ,, z h 大佬!!


素數

定義

就是質數 , ( 1不是素數) , 一個比 1 大的整數若沒有約數(1不算) , 稱為合數

整除

\(a=kd\) ( \(k\) 為整數) , 則稱 \(d\) 整除 \(a\) , 記做 \(d\)

素數計數函式

小於/等於 \(x\) 的素數的個數 , 用 \(\pi(x)\) 表示 , 隨著 \(x\) 的增大 , 有這樣的近似結果 :

\[\pi(x) \sim \frac{x}{ln(x)} \]

素數篩法

暴力 \(O(n\sqrt n)\)

對於檢驗一個數是不是素數 , 暴力做法 --- 列舉 ,, 穩妥 , 但沒必要 , 顯然 , 若 \(x\)

\(a\) 的約數 , 那麼 \(\frac{a}{x}\) 也是 \(a\) 的約數 , 所以對於每對 \((x, \frac ax)\) , 只需檢驗其中一個就可以了 , 其中較小的那個約數在區間 \([1,\sqrt a]\) 裡 , 故單次判斷的時間複雜度為

bool isPrime(a) {
    if (a < 2) return 0; // 1是約數
    for (int i = 2; i * i <= a; i++) 
        if (a % i == 0) return 0;
    return 1;
}

\(\mathfrak{Eratosthenes}\)
篩法 (埃拉託斯特尼篩法 / 埃氏篩) \(O(n~log~log~n)\)

考慮到對於合數 \(x\) , \(x\) 的倍數一定也是合數 , 從小到大篩 , 順手把當前數的倍數標記為合數 , 最後沒被標記 (tag[i] = 1) 的數為素數 , 並且都存到了 \(prime\) 數組裡

void Eratosthenes(int n) { 
    int p = 0;
    for (int i = 0; i <= n; i++) tag[i] = 1;
    tag[0] = tag[1] = 0; //以上為初始化標記
    for (int i = 2; i <= n; i++) {
        if (tag[i]) {
            prime[p++] = i; //p為當前素數數量
            for (int j = i * i; j <= n; j += i)
                tag[j] = 0; //已經篩過了2到i-1的倍數,所以直接從i開始
        } 
    }//需要返回質數個數的話寫成int的函式 return p 即可
}

\(\mathfrak{Euler}\) 篩法 (尤拉篩 / 線性篩) \(O(n)\)

考慮到埃氏篩把一些合數重複篩到 , 所以還不夠優 , 尤拉篩原理 : 用 最小質因子

int pri[n], vis[n];
void init() {
    phi[1] = 1;
    int cnt = 0;
    for(int i = 2; i <= n; i++) {
        if (!vis[i]) {
            phi[i] = i - 1;
            pri[cnt++] = i;
        }
        for (int j = 0; j < cnt; j++) {
            if (1ll * i * pri[j] >= n) break;
            vis[i * pri[j]] = 1;
      if (i % pri[j]) {
        phi[i * pri[j]] = phi[i] * (pri[j] - 1);
      } else {
        // i % pri[j] == 0
        // 換言之,i 之前被 pri[j] 篩過了
        // 由於 pri 裡面質數是從小到大的,所以 i 乘上其他的質數的結果一定也是
        // pri[j] 的倍數 它們都被篩過了,就不需要再篩了,所以這裡直接 break
        // 掉就好了
        phi[i * pri[j]] = phi[i] * pri[j];
        break;
      }
    }
  }
}

未完..