1. 程式人生 > >尤拉函式求法與尤拉篩法求素數

尤拉函式求法與尤拉篩法求素數

尤拉函式:

    尤拉函式定義:

       對於正整數n,尤拉函式Euler(n)是1到n-1中與n互質的數的個數,特別的,Euler(1) = 1,若n為質數則有 Euler(n) = n - 1

     尤拉函式的兩種求法:

    1.由定義和常識可以知道對於任意一個素數n有 Euler(n) = n - 1,對於m = n ^ k,Euler(m)是非常好求解的,顯然,只有n的倍數才是不滿足尤拉函式的定義的數,只要減去即可。得:Euler(m) = n ^ k - n ^ (k - 1)。

         另外,附加介紹以一點關於簡化剩餘系的概念,取定m > 0,若r mod m 中的每個數都與m互素,則稱r mod m是與m互素的剩餘類。從所有與模m互素的剩餘類中各取一數所組成的一組數稱為簡化剩餘系。如 m = 5 有 1 mod 5, 2 mod 5, 3 mod 5, 4 mod 5,是與m互素的剩餘類。易知模m的一個簡化剩餘系中一共有Euler(m)個數,有定理:設(m1, m2) = 1,若x1,x2分別通過模m1,m2的一個簡化剩餘系,則m1x2 + m2x1通過m1m2的一個簡化剩餘系。通過這個定理我們可以得到Euler(m * n) = Euler(m) * Euler(n)。

          對於任意一個數,可以進行整數分解,分解成為 m = n1 ^ x1 * n2 ^ x2 * ni ^ xi,這樣根據上面的兩個結論我們可以得到Euler(m) = Euler(n1 ^ x1) * ... * Euler(ni ^ xi) = ((n1 ^ x1) - (n1 ^ (x1 - 1))) * ... * ((ni ^ xi) - ni ^ (xi - 1)) = (n1 - 1) * (n1 ^ (x1 - 1)) * ... * (ni - 1) * (ni ^ (xi - 1)),根據這個式子我們可以推出如下的性質:設n為m的質因數則有(m % n == 0) (1)若 (m / n) % n != 0 有E(m) = E(m / n) * (n - 1),這裡就是前面式子中每個質因數的前面的(ni - 1)。(2)若有(m / n) % n == 0的話,則是E(m) = E(m / n) *  n,對應的時候前面式子中每一質因數的xi次冪。

          對於上述的式子,可以得到第一種方法來求解尤拉函式。程式碼如下:

int euler(int n){
    int ans = 1;
    for(int i = 2; i * i <= n; i ++){
        if(n % i == 0){
            n /= i;
            ans *= i  - 1;
            while(n % i == 0){
                n /= i;
                ans *= i;
            }
        }
    }
    if(n > 1)
        ans *= n - 1;
    return ans;
}

當然這裡給出尤拉函式的第二種揭解法,我們知道,任何一個數都可以分解成為幾個素數乘積的形式,那麼比如1001 = 7 * 11 * 13,根據我們之前推出的,其實只要減去7,11,13這些質數的倍數,剩下的就都是與其互質的數,那麼這裡涉及到一個新的名詞——容斥定理,容斥定理在這裡的應用是去掉7的倍數,11的倍數,13的倍數,然後加上7和13的倍數,11和13的倍數,7和13的倍數,再減去7和11和13的倍數。

根據容斥定理,有以下程式碼求解尤拉函式:

int euler(int n){
    int ans = n;
    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;
}

尤拉篩法求素數:

    尤拉篩法求素數又稱為線性篩法求素數,是一種將求一定範圍內的素數的時間複雜度控制在O(n)的一種演算法,它跟埃拉特斯尼篩法求素數很像,做了一定的改進,我們知道,埃拉特斯尼篩法之所以沒有達到線性篩的水平是因為它對於同一個合數,要重複操作他的質因子個數次,這是很浪費的,而尤拉篩法求素數則去掉了重複操作,它在當合數i可以整除質數時跳出對下一個數進行操作,比方說,當i = 4時,不會篩掉12這個數因為12找到他的最小的質因數是2,12 = 2 * 6,6定義為它相對最大的合數因子,那麼4不滿足這個條件,所以不在i =4 時篩掉12,而是在i = 6的時候篩掉12.這樣就避免了重複操作。

程式碼如下:

bool IsPrime[1000010];
int Prim[1000010];
int  euler_prime(int n){
    int num = 0, j;
    for(int i = 2; i <= n; i ++){
        if(!IsPrime[i])
            Prim[num ++] = i;
        for(j  = 0; j < num; j ++){
            if(i * Prim[j] > n)
                break;
            IsPrime[i * Prim[j]] = true;
            if(i % Prim[j] == 0)
                break;
        }
    }
    //for(int i = 0; i < num; i ++){
    //    cout << Prim[i] << endl;
    //}
}