數論線性篩總結 (素數篩,尤拉函式篩,莫比烏斯函式篩,前n個數的約數個數篩)

_TCgogogo_發表於2015-08-27

線性篩

 

線性篩在數論中起著至關重要的作用,可以大大降低求解一些問題的時間複雜度,使用線性篩有個前提(除了素數篩)所求函式必須是數論上定義的積性函式,即對於正整數n的一個算術函式 f(n),若f(1)=1,且當a,b互質時f(ab)=f(a)f(b),在數論上就稱它為積性函式,若a,b不互質也滿足的話則稱作完全積性函式,下面說明每個篩子是怎麼篩的。

最基礎的是素數篩,其它三個篩都是以素數篩為前提

 

素數篩

 

void get_prime()  
{  
    int pnum = 0;   
    for(int i = 2; i < MAX; i++)  
    {  
        if(!noprime[i])  
            p[pnum ++] = i;  
        for(int j = 0; j < pnum && i * p[j] < MAX; j++)  
        {  
            noprime[i * p[j]] = true;
            if(i % p[j] == 0)  
                break;  
        }  
    }  
}

主要是break那裡,比如12這個數,在普通篩的時候12要被2和3都篩一次,顯然這種多餘的操作會增加時間複雜度,線性篩中一個數字只被它最小的素因子篩掉,12只被2篩掉,當i等於6的時候2*6==12篩掉12,這時候6%2==0可以break了,如果不break,那麼6還會把18篩掉,此時是通過6*3來篩掉18,可是顯然18最小的素因子是2,所以當i列舉到9的時候有9*2==18,這樣18就又被篩了一次,因此在i等於6的時候不用拿6去篩18,下面用公式來說明:

當p[j]是i的因子時,設i=p[j]*k,因為素因子從小到大列舉,所以p[j]是i的最小素因子,此時i已經無需再去剔除p[j']*i (j'>j) 形式的合數了,因為p[j']*i可以寫成p[j']*(p[j]*k)=p[j]*(p[j']*k),也就是說所有的p[j']*i將會被將來的某個i'=p[j']*k剔除掉,當前的i已經不需要了。

 

 

 

尤拉函式篩

尤拉函式phi[i]表示的是與1-i中與i互質的數的個數。

求解時分三種情況:

1.當i為素數時,顯然phi[i] = i - 1

2.當i % p[j] != 0時,gcd(i, p[j]) = 1,由積性函式的性質可得phi[i * p[j]] = phi[i] * phi[p[j]] = phi[i] * (p[j] - 1)  (p陣列表示素數)

3.當i % p[j] ==0時,根據尤拉函式的求法:phi[n] = n * ∏(1 - 1/p),p為n的質因子,故若i % p[j] == 0,i * p[j]的質因子數不變

則phi[i * p[j]] = i * p[j] * ∏(1 - 1/p) = p[j] * i * ∏(1 - 1/p) = p[j] * phi[i]

由此得到尤拉函式篩:

 

void get_eular()  
{  
    pnum = 0;
    for(int i = 2; i < MAX; i++)  
    {  
        if(!noprime[i])  
        {  
            p[pnum ++] = i;  
            phi[i] = i - 1;  
        }  
        for(int j = 0; j < pnum && i * p[j] < MAX; j++)  
        {  
            noprime[i * p[j]] = true;  
            if(i % p[j] == 0)  
            {  
                phi[i * p[j]] = phi[i] * p[j];  
                break;  
            }  
            phi[i * p[j]] = phi[i] * (p[j] - 1);  
        }  
    }  
} 

 

 

 

 

莫比烏斯函式篩

莫比烏斯函式mob[i]

若i為奇數個不同素數之積mob[i] = -1

若i為偶數個不同素數之積mob[i] = 1

若i有平方因子則mob[i] = 0。

這個做起來比尤拉函式容易,在素數篩上,若i為素數則mob[i] = -1,若i % p[j] == 0,則mob[i * p[j]] = 0,顯然p[j]就是它的平方因子,否則mob[i * p[j]] = -mob[i]

由此得到莫比烏斯函式篩:

 

void Mobius()
{
    int pnum = 0;
    mob[1] = 1;
    for(int i = 2; i < MAX; i++)
    {
        if(!noprime[i])
        {
            p[pnum ++] = i;
            mob[i] = -1;
        }
        for(int j = 0; j < pnum && i * p[j] < MAX; j++)
        {
            noprime[i * p[j]] = false;
            if(i % p[j] == 0)
            {
                mob[i * p[j]] = 0;
                break;
            }
            mob[i * p[j]] = -mob[i];
        }
    }
}

 

 

 

 

前n個數的約數個數篩

facnum[i]表示i的約數個數

通過素數篩得到前n個數的約數個數非常巧妙,首先根據約數個數定理:

對於一個大於1正整數n可以分解質因數:n = p1^d1 + p2^d2 + ... + pk^dk,其中pi為素數

則n的正約數的個數就是

:facnum[n] = (1 + d1) * (1 + d2) * ... * (1 + dk)
我們需要一個輔助陣列d[i],表示i的最小質因子的次冪,(最小的原因是素數篩裡每次都是用最小的質因子來篩合數的),還是三種情況:
1.當i為素數時,facnum[i] = 2;d[i] = 1,很好理解
2.當i % p[j] != 0時,gcd(i, p[j]) =1,由積性函式的性質可得facnum[i * p[j]] = facnum[i] * facnum[p[j]] = facnum[i] * 2
   d[i * p[j]] = 1(無平方因子)
3.當i % p[j] == 0時,出現平方因子,最小質因子的次冪加1,因此有facnum[i * p[j]] = facnum[i] / (d[i] + 1) * (d[i] + 2)
   d[i * p[j]] = d[i] + 1
由此得到前n個數的約數個數篩:

 

 

void get_facnum()
{
    int pnum = 0;
    facnum[1] = 1;
    for(int i = 2; i < MAX; i++)
    {
        if(!noprime[i])
        {
            p[pnum ++] = i;   
            facnum[i] = 2;  
            d[i] = 1;      
        }
        for(int j = 0; j < pnum && i * p[j] < MAX; j++)
        {
            noprime[i * p[j]] = true;
            if(i % p[j] == 0)
            {
                facnum[i * p[j]] = facnum[i] / (d[i] + 1) * (d[i] + 2); 
                d[i * p[j]] = d[i] + 1; 
                break;
            }
            facnum[i * p[j]] = facnum[i] * 2;
            d[i * p[j]] = 1; 
        }
    }
}

 

 

 

四合一

 

void get_all()
{
    int pnum = 0;
    phi[1] = 1;
    mob[1] = 1;
    facnum[1] = 1;
    for(int i = 2; i < MAX; i++)
    {
        if(!noprime[i])
        {
            phi[i] = i - 1;
            mob[i] = -1;
            p[pnum ++] = i;   
            facnum[i] = 2;  
            d[i] = 1;      
        }
        for(int j = 0; j < pnum && i * p[j] < MAX; j++)
        {
            noprime[i * p[j]] = true;
            if(i % p[j] == 0)
            {
                phi[i * p[j]] = phi[i] * p[j];
                mob[i * p[j]] = 0;
                facnum[i * p[j]] = facnum[i] / (d[i] + 1) * (d[i] + 2); 
                d[i * p[j]] = d[i] + 1; 
                break;
            }
            phi[i * p[j]] = phi[i] * (p[j] - 1);
            mob[i * p[j]] = -mob[i];
            facnum[i * p[j]] = facnum[i] * 2;
            d[i * p[j]] = 1; 
        }
    }
}

 

 

 

最後吐槽一下,對於素數篩裡的判斷函式,最好用noprime,因為全域性預設值為false,有的時候MAX為1e7之類的,memset成true也費不少

 

python 素數篩

 

n = raw_input()
n = int(n)
isPrime = [True] * (n + 1)
prime = [0] * (n + 1)
cnt = 0
isPrime[0] = isPrime[1] = False

for i in range(2, n) :
    if isPrime[i] :
        prime[cnt] = i
        cnt = cnt + 1
    for j in range (0, cnt) :
        if (i * prime[j] > n) :
            break;
        isPrime[i * prime[j]] = False;
        if (i % prime[j] == 0) :
            break

for i in range(0, cnt) :
    print prime[i]

 

 

 

 

 

相關文章