1. 題目來源
鏈接:79. 輸出素數
2. 題目說明
3. 題目解析
方法一:素數篩+巧妙解法
題意很明確,這應該是學習完語言基礎,甚至是循環判斷基礎後就應該會做的題目。但是會做不代表能在這道題目上拿到滿分。
光是這一道題目就能產生 5 個層次的學生,看看你在哪一層呢?
第一層:0 分,不會寫,過不了,沒思路
第二層:40 分,最暴力的方式解決,但忘記考慮邊界 0 1情況,仍採用 cin、cout
,但數據不強,在此不受影響
參見代碼如下:
#include <iostream>
#include <cmath>
using namespace std;
bool is_prime(int x) {
for (int i = 2; i < x; ++i) {
if (x % i == 0) return false;
}
return true;
}
int main() {
int a, b;
cin >> a >> b;
for (int i = a; i <= b; ++i) {
if (is_prime(i)) cout << i << endl;
}
return 0;
}
第三層:50分,算是及格分了,最暴力的方式解決,考慮邊界 0 1情況,仍採用 cin、cout
,但數據不強,在此不受影響
參見代碼如下:
#include <iostream>
#include <cmath>
using namespace std;
bool is_prime(int x) {
// 加一行,多10分
if (x == 0 or x == 1) return false;
for (int i = 2; i < x; ++i) {
if (x % i == 0) return false;
}
return true;
}
int main() {
int a, b;
cin >> a >> b;
for (int i = a; i <= b; ++i) {
if (is_prime(i)) cout << i << endl;
}
return 0;
}
第四層:70分,算是及格分了,開根號優化,考慮邊界 0 1情況,仍採用 cin、cout
,但數據不強,在此不受影響,在此即便換成 scanf、printf
也僅有 70 分
參見代碼如下:
#include <iostream>
#include <cmath>
using namespace std;
bool is_prime(int x) {
if (x == 0 or x == 1) return false;
for (int i = 2; i * i <= x; ++i) {
if (x % i == 0) return false;
}
return true;
}
int main() {
int a, b;
cin >> a >> b;
for (int i = a; i <= b; ++i) {
if (is_prime(i)) cout << i << endl;
}
return 0;
}
第五層:100分,完美的解答,採用素數篩,優化效率
這個也就是本篇博文的意義所在了,素數篩 算法,思想很簡單,就考你知道還是不知道。在此首先來看看 一般篩,也就是 埃氏篩。下面依據紫書的 10.1.2節有關講解進行說明。篩法的思想特別簡單:對於不超過 n
的每個非負整數 p
,刪除 2p, 3p, 4p,…
當處理完所
有數之後,還沒有被刪除的就是素數。如果用 vis[i]
表示i已經被刪除,篩法的代碼可以寫成:
memset(vis, 0, sizeof(vis));
for(int i = 2; i <= n; ++i)
for(int j = i*2; j <= n; j += i) vis[j] = 1;
循環總次數會小於,時間複雜度爲 效率已然很高了,但這樣會重複篩去,2*3=6
和 3*2=6
會重複篩,但即便這樣,效率仍接近線性。有興趣可自由證明。
在此掛上大佬鏈接:普通篩法時間界的證明
下面來改進這份代碼。首先,在 對於不超過 n
的每個非負整數 p
中,p
可以限定爲素數,只需在第二重循環前加一個判斷 if(!vis[i])
即可。另外,內層循環也不必從 i*2
開始,它已經在 i=2
時被篩掉了。改進後的代碼如下:
memset(vis, 0, sizeof(vis));
for(int i = 2; i * i <= n; ++i) if(!vis[i])
for(int j = i*i; j <= n; j += i) vis[j] = 1;
完成版如下,但仍只能獲取 70 分,但是這並不是算法的問題,而是 cin、cout
太拉胯了,得改成 scanf、printf
,就好了。
參見代碼如下:
#include <iostream>
#include <cmath>
using namespace std;
const int MAXN = 1e7 + 50;
int prime[MAXN];
void init_prime() {
prime[0] = prime[1] = 1;
for (int i = 2; i * i <= MAXN; ++i) {
if (prime[i]) continue;
for (int j = 2 * i; j <= MAXN; j += i) {
prime[j] = 1;
}
}
return;
}
int main() {
init_prime();
int a, b;
cin >> a >> b;
for (int i = a; i <= b; ++i) {
if (prime[i] == 0) cout << i << endl;
}
return 0;
}
將 cin、cout
,改成 scanf、printf
,終於獲取滿分。
參見代碼如下:
#include <iostream>
#include <cmath>
using namespace std;
const int MAXN = 1e7 + 50;
int prime[MAXN];
void init_prime() {
prime[0] = prime[1] = 1;
for (int i = 2; i * i <= MAXN; ++i) {
if (prime[i]) continue;
for (int j = 2 * i; j <= MAXN; j += i) {
prime[j] = 1;
}
}
return;
}
int main() {
init_prime();
int a, b;
scanf("%d%d", &a, &b);
for (int i = a; i <= b; ++i) {
if (prime[i] == 0) printf("%d\n", i);
}
return 0;
}
方法二:線性篩+巧妙解法
線性篩即 歐拉篩,將素數判斷的時間複雜度優化至 ,其思想仍繼承自上述的埃氏篩,埃氏篩的效率已經很高了,但其不足之處在於仍存在很多數被重複判斷,做了這些不必要的操作必然影響效率。歐拉篩針對這點進行改善。
參見代碼如下:
#include <iostream>
#include <string.h>
#include <cmath>
using namespace std;
const int MAXN = 1e7 + 50;
int prime[MAXN], c = 0;
bool number[MAXN];
void init_prime(int n) {
for (int i = 2; i <= n; i++) {
if (!number[i]) prime[c++] = i;
for (int j = 0; j < c and prime[j] * i <= MAXN; j++) {
number[prime[j] * i] = true;
//保證每個合數只會被它的最小質因數篩去,因此每個數只會被標記一次
if (i % prime[j] == 0)
break;
}
}
}
int main() {
int a, b;
scanf("%d%d", &a, &b);
init_prime(b);
for (int i = 0; i < c; ++i) {
if (prime[i] < a) continue;
else printf("%d\n", prime[i]);
}
return 0;
}
prime
數組存放小於 n
的所有素數,其個數爲 c
個。prime
數組中的素數是遞增的,當 i
能整除 prime[j]
,那麼 i * prime[j + 1]
這個合數肯定被 prime[j]
乘以某個數篩掉。因爲 i
中含有 prime[j]
,prime[j]
比 prime[j+1]
小,即 i=k*prime[j]
,那麼 i*prime[j+1]=(k*prime[j])*prime [j+1]=k’*prime[j]
,接下去的素數同理。所以不用篩下去了。因此,在滿足 i%prime[j]==0
這個條件之前以及第一次滿足改條件時,prime[j]
必定是 prime[j]*i
的最小因子。故不會對一個元素進行重複判斷,將效率提升到 。
方法三:Miller-Rabbin 素數測試+巧妙解法
這個方法是在前段時間做一個關於 RSA
加密項目瞭解到的,適合大數的素性測試,500 位以上的大數速度也是很快的,在 boost
庫中自帶有大數及 Miller-Rabbin
素數測試函數接口,配合起來使用實在是太方便了。它基於隨機算法,可以在 內判斷一個數是否是素數,但存在一定的誤差。其中包含有一部分基礎數論、概率相關知識,就不在這裏展開詳解了,點到此地,有興趣可自行了解。