天天看點

線性篩素數、歐拉函數

判斷一個數n是否是素數,衆所周知可以用O(sqrt(n))的方法。

但是如果要求很多個數,這個方法就不太好了。(比如所有小于n的數,複雜度就是O(n1.5)。)

埃拉托斯特尼篩法,大家都聽說過。從2到n,去掉每個數的倍數,剩下來的就是質數。

不過這個方法會重複删除,比如6是2、3的倍數,會被删2次,因子越多,删的次數就越多。

改進之後的線性篩保證每個數隻被最小的質因子删,是以是O(n)的。

#include<cstdio>
#include<cstring>
#define MAXN 100005
#define MAXL 1299710
int prime[MAXN];
int check[MAXL];

int tot = 0;
memset(check, 0, sizeof(check));
for (int i = 2; i < MAXL; ++i)
{
  if (!check[i])
  {
    prime[tot++] = i;
  }
  for (int j = 0; j < tot; ++j)
  {
    if (i * prime[j] > MAXL)
    {
      break;
    }
    check[i*prime[j]] = 1;
    if (i % prime[j] == 0)
    {
      break;
    }
  }
}      

View Code

tot是計數用的,prime儲存質數,check是判斷是否是質數。

1.任意一個合數 A = p1p2...pn,(其中p1<=p2<=...<=pn) ,i=p2p3...pn時會删掉。

2.A隻會被p1删掉。若i是prime[j]的倍數,A不會被p[j+1]删掉,當i=A/p[j+1]時,i%p[j+1]==0,break。如果不退出,A就被p[j+1]删了。

可以看出,這個方法需要額外的prime數組。而埃氏篩不必要。

順便可以求歐拉函數

#include<cstdio>
#include<cstring>
#define MAXN 100005
#define MAXL 1299710
int prime[MAXN];
int check[MAXL];
int phi[MAXL];
int tot = 0;
phi[1] = 1;
memset(check, 0, sizeof(check));
for (int i = 2; i < MAXL; ++i)
{
  if (!check[i])
  {
    prime[tot++] = i;
    phi[i] = i - 1;
  }
  for (int j = 0; j < tot; ++j)
  {
    if (i * prime[j] > MAXL)
    {
      break;
    }
    check[i*prime[j]] = 1;
    if (i % prime[j] == 0)
    {
      phi[i*prime[j]] = phi[i] * prime[j];
      break;
    }else
    {
      phi[i*prime[j]] = phi[i] * (prime[j]-1);
    }
  }
}      

View Code

n為質數,phi(n)=n-1

n為合數,進行質因數分解。$\large n = p_1^{k_1}\times p_2^{k_2}\times ... \times p_n^{k_n}$

$$\large \varphi(n) = n \prod\limits_{i = 1} ^ {n} \frac{p_i - 1}{p_i}$$

篩掉n=i*prime[j]時,求$\varphi(n)$,篩和求是同步的,也是O(n)。

設p1是n的最小質因子,n'=n/p1。

若n'%p1=0,即k1>1,n'含有n的所有質因子,則有

 $$\large \begin{align} \varphi(n)&= n \prod\limits_{i = 1} ^ {n} \frac{p_i - 1}{p_i} \\ &= p_1 \times n' \prod\limits_{i = 1} ^ {n} \frac{p_i - 1}{p_i} \\ &= p_1 \times \varphi(n') \\ \end{align}$$

若n'%p1≠0,即k1=1,n'與p1互質,有

$$\large \begin{align} \varphi(n)&= n \prod_{i = 1} ^ {n} \frac{p_i - 1}{p_i} \\ &= p_1 \times n' \times \frac{p_1-1}{p_1} \prod\limits_{i = 2} ^ {n} \frac{p_i - 1}{p_i} \\ &= (p_1-1) \times \varphi(n') \\ \end{align}$$

 (這也證明了歐拉函數是積性函數)

ref:

代碼全是抄這裡的。

歐拉函數的公式全是抄這裡的。

延伸:

隻篩奇數。

我推薦這個算法! 易于了解。 隻算奇數部分,時空效率都還不錯!  
half=SIZE/2;   
int sn = (int) sqrt(SIZE);   
for (i = 0; i < half; i++)   
   p[i] = true;// 初始化全部奇數為素數。p[0]對應3,即p[i]對應2*i+3   
for (i = 0; i < sn; i++) {      
if(p[i])//如果 i+i+3 是素數  
{       //每次j+=k,2*(j+k)+3=(2*j+3)+2k,也就是在上一個篩的數上+2k
    for(k=i+i+3, j=k*i+k+i; j < half; j+=k)   
    // 篩法起點是 p[i]所對應素數的平方 k^2                                          
    // k^2在 p 中的位置是 k*i+k+i  
    //    下标 i         k*i+k+i  
    //對應數值 k=i+i+3   k^2           
       p[j]=false;   
}   
}   
//素數都存放在 p 數組中,p[i]=true代表 i+i+2 是素數。  
//舉例,3是素數,按3*3,3*5,3*7...的次序篩選,因為隻儲存奇數,是以不用删3*4,3*6....       

View Code

來自:https://blog.csdn.net/dinosoft/article/details/5829550

轉載于:https://www.cnblogs.com/azureice/p/linear-sieve-for-prime-numbers.html