Algorithm: Prime & Euler Function & Productive Function

素數篩

樸素算法

通常來講,能夠用試除法判斷某一個數是否是素數:ios

bool isPrime(int n) {
    if(n < 2) return false;
    for(int i = 2; i < n; i++)
        if(n % i == 0) return false;
    return true;
}

但其實咱們只須要試除到根號n便可,由於對於任意的n,假設存在一個大於根號n的因數,那麼確定存在一個小於根號n的因數與之對應。那麼有:算法

bool isPrime(int n) {
    if(n < 2) return false;
    int m = sqrt(n + .5);
    for(int i = 2; i <= m; i++)
        if(n % i == 0) return false;
    return true;
}

埃氏篩

但若是咱們要求全部小於等於n的數是否是素數呢?這時咱們用素數篩法解決。篩法是一種思想,利用以前處理過的信息來更新後面的結果。對於一個大於1的數,它的倍數顯然是合數,那麼咱們能夠在遍歷到i時,篩去全部i的倍數;當咱們遍歷到i+1時,只要它尚未被篩去,那它就必定是素數。這就是埃拉託斯特尼篩法,簡稱埃氏篩函數

int prime[M];
bool vis[M];
void eratosthenes() {
    for (int i = 2; i < M; i++) {
        if (!vis[i]) {
            prime[++prime[0]] = i;
            for (int j = 2 * i; j < M; j += i) {
                vis[j] = 1;
            }
        }
    }
}

改進埃氏篩

顯然埃氏篩有不少的重複的篩除操做,把樸素算法中的優化引入埃氏篩,咱們就能夠獲得改進的埃氏篩。優化

int prime[M];
bool vis[M];
void eratosthenes_plus() {
    int m = sqrt(M + .5);
    for (int i = 2; i <= m; i++) {
        if (!vis[i]) {
            prime[++prime[0]] = i;
            for (int j = i * i; j < M; j += i) {
                vis[j] = 1;
            }
        }
    }
}

歐拉篩

雖然改進的埃氏篩複雜度在大多數狀況已經能夠被接受了,但有時咱們須要線性時間判斷1~n的全部素數。這時就須要歐拉篩。歐拉篩經過保證每一個素數都會被它最小的那個(質)因數篩掉,進而使複雜度達到線性。ui

int prime[M];
bool vis[M];
void euler() {
    for(int i = 2; i < M; i++) {
        if(!vis[i]) {
            prime[++prime[0]] = i;
        }
        for(int j = 1; j <= prime[0] && i * prime[j] < M; j++) {
            vis[i * prime[j]] = 1;
            if(i % prime[j] == 0) {
                break;
            }
        }
    }
}

重點就在於if(i % prime[j] == 0)這一句,爲何要在i是第j個素數的倍數時中止篩除呢?
\[ \begin{align} & 當i是prime[j]的倍數時,咱們能夠把i表示爲i=k\times prime[j],k\in N^+。\\ & 對於下一個被篩去的數X,X=i\times prime[j+1]=k\times prime[j]\times prime[j+1],\\ & 顯然X還存在一個更小的質因子prime[j],爲了符合每一個數都被最小的質因數篩去的原則,\\ & 此時應該跳出循環,不用prime[j+1]\times(k\times prime[j])篩去X,而應該在未來,\\ & 用prime[j]\times(k\times prime[j+1])篩去X。 \end{align} \]spa

歐拉函數與歐拉定理

歐拉函數

在數論中,對正整數n,歐拉函數φ(n)的值爲小於或等於n的正整數中與n互質的數的個數。.net

例如:φ(8)=4,由於1,3,5,7與8互質。code

擴展:在羣論中,歐拉函數其實是模n的同餘類構成的乘法羣的階。blog

歐拉定理

在數論中,歐拉定理爲:若gcd(a,n)=1,則:
\[ a^{\phi(n)}\equiv 1(mod\space n) \]
歐拉函數的性質和拉格朗日陪集定理結合構成了歐拉定理的證實。ci

推廣:歐拉降冪公式
\[ a^b\equiv a^{b\%\phi(n)+\phi(n)}(mod\space n) \]

例題:洛谷 5091

性質

基本性質

若n是質數p的k次冪,那麼有:
\[ \phi(n)=\phi(p^k)=p^k-p^{k-1}=(p-1)p^{k-1}\\ p^{k-1}爲1到n中p的倍數,顯然這些數與n不互質。 \]
例:
\[ \phi(72)=\phi(2^3\times 3^2)=2^{3-1}\times (2-1)\times 3^{2-1}\times (3-1)=24 \]

費馬小定理的推廣

回顧費馬小定理:若p爲質數,a爲任意正整數,那麼:
\[ a^p-a能夠被p整除。 \]
即:
\[ \begin{aligned} &a^p-a\equiv0(mod\space p)\\ \Leftrightarrow&a^p\equiv a(mod\space p)\\ \Leftrightarrow&a^{p-1}\equiv 1(mod\space p) \end{aligned} \]
回到歐拉定理,若p爲質數,φ(p)=p-1,那麼有:
\[ a^{p-1}\equiv 1(mod\space p) \]
這就是費馬小定理。歷史上,歐拉首先證出了費馬小定理,而後在這個基礎上推廣獲得了歐拉定理。

積性函數

即若m,n互質,那麼有φ(mn)=φ(m)φ(n)

推廣:小於n的全部與n互質的數的和爲n*φ(n)/2。

對任意a>b>0,gcd(a,b)=1,總有gcd(a,a-b)=1。那麼對於n,有φ(n)個小於n的數與n互質,設其爲x,那麼總存在一個n-x也與n互質,二者之和爲n,那麼一共有φ(n)/2對。這些數的和爲n*φ(n)/2。

例:hdu 3501

題意:求比n小的、和n不互質的數的和%1,000,000,007,其中n≤10的9次方。

歐拉函數計算

對於任意正整數n,分解質因數得:
\[ n=p_1^{k_1}\times p_2^{k_2}\times ......\times p_m^{k_m} \]
由:
\[ \phi(p^k)=p^k-p^{k-1}=p^k(1-{1\over p}) \]
得:
\[ \phi(n)=p_1^{k_1}(1-{1\over p_1})\times p_2^{k_2}(1-{1\over p_2})\times ......\times p_m^{k_m}(1-{1\over p_m}) \]
又:
\[ n=\prod_{i=1}^m p_i^{k_i} \]
因此:
\[ \phi(n)=n\times \prod_{i=1}^m (1-{1\over p_i}) \]
代碼實現以下:(例題:poj 2407)

// 計算單個歐拉函數值
int euler(int n) {
    int ans = n;
    // 追求更高效率還能夠結合素數表
    int m = sqrt(n + .5);
    for(int i = 2; i * i <= n; i++) {
        if(n % i == 0) {
            ans -= ans / i;
            while(n % i == 0) n/= i;
        }
    }
    if(n > 1) ans -= ans / n;
    return ans;
}

下面是打表寫法。

LL euler[N];
void cal_euler() {
    euler[1] = 1;
    for(int i = 2; i <N; i++) {
        if(!euler[i]) {
            for(int j = i; j < N; j += i) {
                if(!euler[j]) euler[j] = j;
                euler[j] -= euler[j] / i;
            }
        }
    }
}

例題:hdu 2824

題意:歐拉前綴和。注意空間限制。

#include <algorithm>
#include <iostream>
using namespace std;
typedef long long LL;
const int N = 3e6 + 100;
LL euler[N];
void pre() {
    euler[1] = 1;
    for(int i = 2; i <N; i++) {
        if(!euler[i]) {
            for(int j = i; j < N; j += i) {
                if(!euler[j]) euler[j] = j;
                euler[j] = euler[j] / i * (i - 1);
            }
        }
    }
    for(int i = 2; i < N; i++) {
        euler[i] += euler[i - 1];       
    }
}
int main() {
    pre();
    int a, b;
    while(cin >> a >> b) {
        cout << euler[b] - euler[a - 1] << endl;
    }
    return 0;
}

積性函數

定義

在數論中,積性函數是指定義在正整數集上的算數函數f(n),且有:f(1)=1;若gcd(a,b)=1,f(ab)=f(a)f(b)。

擴展:徹底積性函數:若積性函數f在gcd(a,b)≠1時,仍有f(ab)=f(a)f(b),那麼f稱爲徹底積性函數。在數論外的積性函數通常是指徹底積性函數。

性質

對n質因分解得:
\[ n=\prod^k_{i=1} p_i^{a_i},其中,p_i爲分解獲得的質因數 \]
那麼對於積性函數f,有:
\[ f(n)=\prod^k_{i=1}f(p_i^{a_i}) \]

常見的積性函數

歐拉函數、莫比烏斯函數、gcd(n,k)(k固定),約數函數σ(σ(n)爲n的約數個數)。約數個數函數σ定義以下:

對於任意正整數n,設其質因分解爲:
\[ n=p_1^{k_1}\times p_2^{k_2}\times ......\times p_m^{k_m} \]
那麼其因數個數爲:
\[ N=(k_1+1)\times(k_2+1)\times...\times(k_m+1) \]

\[ 即:σ(n)=\prod_{i=1}^m (k_i+1) \]

相關文章
相關標籤/搜索