求解素數演算法(leetcode有感而發)

2021-10-07 23:23:27 字數 4584 閱讀 8480

總結了一些常見的判定素數和計算某個範圍內素數個數的一些演算法。

根據定義,判斷乙個整數n是否是素數,只需要去判斷在整數區間[2, n-1]之內,是否具有某個數m,使得n % m == 0。

int

isprime

(int n)

return1;

}

事實上,這個演算法是o(n)的,感覺是很快了,但是有乙個演算法是o(sqrt(n))的演算法。**如下:

int

isprime

(long

long n)

return1;

}

原理很巧妙,也僅僅是把**的i < n變成了i * i <= n而已,但是優化卻是極其高的。可能你會注意到,在上乙份**裡面,我定義的n為int型別,而後面乙份**,卻定義成了long long,這是因為在1s內,上乙份**能判斷出來的數量級為1e8,而後面乙份**判斷出來的數量級卻幾乎可以達到1e16。而原因僅僅是因為a * b = c的話一旦a是c的約數,那麼b也是,如果a不是,那麼b也不是。

這個方法也可以稱作試除法

儘管上面的o(sqrt(n))的演算法已經非常優秀了,但是面對更高數量級的「大數」卻會顯得力不從心。而這個時候,miller_rabin的優越性就顯示出來了。

miller_rabin的理論基礎**於費馬小定理。值得一提的是費馬小定理是數論四大定理之一。

費馬小定理:n是乙個奇素數,a是任何整數(1≤ a≤n-1) ,則 a^(n-1)≡1(mod n)

要測試n是否是乙個素數,首先將n-1分解為(2^s) * d,在每次測試開始時,先隨機選擇乙個介於[1, n - 1]的整數a,之後如果對於所有的r∈[0, s - 1],若a^d mod n ≠ 1且a((2r) * d) mod n ≠ -1,那麼n就是乙個合數,否則n有3/4的機率是素數。

這也是為什麼說這個演算法只是素性測試了,他不能完全保證結果正確,但是當測試次數大約為20的時候,基本可以確保正確率達到97%以上。

它的**可以這麼寫:

const

int s =20;

ll mod_mul

(ll a, ll b, ll n)

return res;

}ll mod_exp

(ll a, ll b, ll n)

return res;

}bool

miller_rabin

(ll n)

srand

((ll)

time

(null))

;for

(int i =

0; i < s;

++i)

if(x !=1)

return

false;}

return

true

;}

上面介紹的一些素數判斷的演算法,在某些問題是基本可以適用的了。但是對於另外一類問題卻十分尷尬。比如問你整數區間[1, n]內的素數個數是多少。這個時候如果乙個個列舉,乙個個判斷,對於樸素方法來說,最優也是o(nsqrt(n)),即使是miller_rabin演算法也無法在o(n)的時間內得到結果。於是就有了埃氏篩選法(埃拉託斯特尼篩法)

對於篩選整數n以內的素數,演算法是這麼描述的:先把素數2的倍數全部刪除,剩下的數第乙個為3,再把素數3的倍數全部刪除,剩下的第乙個數為5,再把素數5的倍數全部刪除······直到把n以內最後乙個素數的倍數刪除完畢,得到n以內的所有素數。

const

int maxn =

1e7+5;

int pri[maxn]

;void

getprime

(int n)

}}

#include

using

namespace std;

const

int maxn =

1e7+5;

int pri[maxn]

;void

getprime

(int n)}}

intmain()

return0;

}

而事實上,觀察可以發現,上面的這種寫法有很多次重複計算,這樣顯然無法做到線性篩選,而另外一種寫法卻可以得到線性篩選,達到時間複雜度為o(n),**如下:

const

int maxn =

1e7+5;

//**有些不理解

void

getprime()

}}

#include

#include

using

namespace std;

const

int maxn =

1e2;

//**有些不理解

來自kuangbin的模板。模板看的不是很明白,下面我自己寫的,比較簡單容易明白

int

countprimes

(int n)}}

return count;

}

從上面的**可以發現,顯然這種篩法只能應付達到1e7這種數量級的運算,即使是線性的篩選法,也無法滿足,因為在acm競賽中,1e8的記憶體是極有可能獲得memery limit exceed的。

於是可以考慮容斥原理。

以ahuoj 557為例,1e8的情況是篩選法完全無法滿足的,但是還是考慮a * b = c的情況,1e8只需要考慮10000以內的素數p[10000],然後每次先減去n / p[i],再加上n / (p[i] * p[j])再減去n / (p[i] * p[j] * p[k])以此類推…於是就可以得到正確結果了。

**如下:

#include

#include

using

namespace std;

const

int maxn =

10005

;int sqrn, n, ans =0;

bool vis[maxn]

;int pri[

1500]=

;void

init()

}}void

dfs(

int num,

int res,

int index)

dfs(num +

1, res * pri[i]

, i+1)

;if(num %2==

1)else

if(num ==

1) ans++;}

}int

main()

return0;

}

最後介紹的這個演算法可以說是黑科技級別的,它能夠在3s內求出1e11之內的素數個數。據說還有在300ms內求出1e11的個數的。可以參考wiki裡面原理。然後給出來自codeforces 665f題目裡面的**。

#define maxn 100    

// pre-calc max n for phi(m, n)

#define maxm 10010

// pre-calc max m for phi(m, n)

#define maxp 40000

// max primes counter

#define max 400010

// max prime

#define setbit(ar, i) (((ar[(i) >> 6]) |= (1 << (((i) >> 1) & 31))))

#define chkbit(ar, i) (((ar[(i) >> 6]) & (1 << (((i) >> 1) & 31))))

#define isprime(x) (( (x) && ((x)&1) && (!chkbit(ar, (x)))) || ((x) == 2))

namespace pcf

;int len =

0, primes[maxp]

, counter[max]

;void

sieve()

}for

(int i =

1; i < max; i++)}

void

init()

}}long

long

phi(

long

long m,

int n)

long

long

lehmer

(long

long m)

}

題目例項:

leetcode 204. 計數質數

求解素數問題演算法

在解程式題的過程中經常會遇到素數的判斷問題,在保證不能超時的同時要判斷是否為素數,大多數初學者會採用第一種演算法 1.單單列出演算法,不列出完整程式 include include intf int n if a 0 test if test n return i 這就是求第n個素數的演算法 姑且稱...

素數求解(100 200)

素數又稱質數,有無限個。乙個大於1的自然數,除了1和它本身外,不能被其他自然數整除,換句話說就是該數除了1和它本身以外不再有其他的因數 否則稱為合數。方法1 設100 200間的這個數是i,用i去分別除以2 i 1 間的每乙個數。include includeint main 定義型別,整型 if ...

關於素數的求解

質數 prime number 又稱素數,有無限個。乙個大於1的自然數,除了1和它本身外,不能被其他自然數整除,換句話說就是該數除了1和它本身以外不再有其他的 因數。求解整數n之前的所有素數及總數 include stdio.h include math.h int main if c sqrt i...