洛谷P3383 【模板】線性篩素數

Tomorrowland_D發表於2024-07-24

【模板】線性篩素數

題目背景

本題已更新,從判斷素數改為了查詢第 k 小的素數
提示:如果你使用 cin 來讀入,建議使用 std::ios::sync_with_stdio(0) 來加速。

題目描述

如題,給定一個範圍 n,有 q 個詢問,每次輸出第 k 小的素數。

輸入格式

第一行包含兩個正整數 n,q,分別表示查詢的範圍和查詢的個數。

接下來 q 行每行一個正整數 k,表示查詢第 k 小的素數。

輸出格式

輸出 q 行,每行一個正整數表示答案。

樣例 #1

樣例輸入 #1

100 5
1
2
3
4
5

樣例輸出 #1

2
3
5
7
11

提示

【資料範圍】
對於 100% 的資料,n = 108,1<=q<=106,保證查詢的素數不大於 n。

Data by NaCly_Fish.

埃氏篩法求質數

埃氏篩(Sieve of Eratosthenes)是一種用來找出一定範圍內所有素數的經典演算法。它由古希臘數學家埃拉託斯特尼斯(Eratosthenes)發明,用於解決尋找素數的問題。

演算法原理

埃氏篩的基本思想是:

  1. 初始化一個布林型別的陣列,稱為標記陣列(或篩選陣列),用來標記每個整數是否為素數。陣列的下標表示整數,陣列的值為 true 表示該下標對應的整數是素數,為 false 表示不是素數。
  2. 從小到大依次遍歷每個整數 i,若發現 i 是素數,則將 i 的所有倍數標記為非素數(即將對應位置的布林值設為 false),除了 i 本身。

具體步驟如下:

  • 初始化一個布林陣列 is_prime,將陣列中所有元素初始化為 true
  • is_prime[0]is_prime[1] 設為 false,因為 0 和 1 不是素數。
  • 2 開始遍歷到 sqrt(n),對於每個素數 i,將 i 的所有倍數(除了 i 本身)設為 false
  • 最終,所有值為 true 的下標即為素數。

演算法最佳化

  • 減少遍歷範圍: 在埃氏篩中,我們只需要遍歷到 sqrt(n) 就可以了,因為如果 in 的因子,那麼 n/i 也一定是 n 的因子。
  • 空間最佳化: 如果要找出的素數範圍不是很大,可以最佳化空間。例如,使用標記陣列只標記奇數,可以將空間使用減半。

示例

vector<int> sieve(int n) {
	vector<bool> is_prime(n + 1, true);
	vector<int> primes;
	//這裡i不能只遍歷到sqrt(n)就結束
	//這裡的條件 i <= n / i 可能會導致問題。當 i 很大時,n / i 可能會得到一個非常小的數,比如當 i 接近 sqrt(n) 時,
	// n / i 可能會變成0或者1,這樣迴圈就會提前結束,導致部分素數沒有被正確標記。
	for (int i = 2; i <= n; ++i) {
		if (is_prime[i]) {
			primes.push_back(i);
			for (long long j = (long long)i * i; j <= n; j += i) {
				is_prime[j] = false;
			}
		}
	}
	return primes;
}

有了這個模板以後,這道題我們直接套模板就可以解決了:

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
vector<int> sieve(int n) {
	vector<bool> is_prime(n + 1, true);
	vector<int> primes;
	//這裡i不能只遍歷到sqrt(n)就結束
	//這裡的條件 i <= n / i 可能會導致問題。當 i 很大時,n / i 可能會得到一個非常小的數,比如當 i 接近 sqrt(n) 時,
	// n / i 可能會變成0或者1,這樣迴圈就會提前結束,導致部分素數沒有被正確標記。
	for (int i = 2; i <= n; ++i) {
		if (is_prime[i]) {
			primes.push_back(i);
			for (long long j = (long long)i * i; j <= n; j += i) {
				is_prime[j] = false;
			}
		}
	}
	return primes;
}

int main() {

	ios::sync_with_stdio(0);
	cin.tie(nullptr);

	int n, q;
	cin >> n >> q;

	vector<int> primes = sieve(n);

	while (q--) {
		int k;
		cin >> k;
		// 輸出第 k 小的素數
		cout << primes[k - 1] << "\n";
	}

	return 0;
}

程式碼解釋:

ios::sync_with_stdio(0);
cin.tie(nullptr);

這兩行程式碼是關於 C++ 標準輸入輸出流的設定,它們可以用來最佳化輸入輸出的效能。

  1. ios::sync_with_stdio(false);

    這一行程式碼用於取消 cincout 的同步。在預設情況下,C++ 的 cincout 是同步的,這意味著每次呼叫 cin 時,會先重新整理 cout 的緩衝區,確保輸出的內容被正確顯示。但是這種同步可能會帶來一些效能開銷,特別是在大量輸入輸出操作時。

    當你呼叫 ios::sync_with_stdio(false); 時,表示取消 cincout 的同步,這樣可以加快輸入輸出的速度。但是請注意,取消同步後,使用 cincout 時可能需要顯式地處理緩衝區重新整理的問題,以避免輸出不及時或亂序輸出的情況。

  2. cin.tie(nullptr);

    這行程式碼用於斷開 cincout 的繫結。在預設情況下,cincout 是繫結在一起的,這意味著當你使用 cin 進行輸入時,cout 的緩衝區會被重新整理。透過呼叫 cin.tie(nullptr);,你可以將 cincout 的繫結解除,進一步減少不必要的效能開銷。

綜上所述,這兩行程式碼的主要作用是最佳化 C++ 的標準輸入輸出流,以提高程式的效能和效率,特別是在處理大量輸入輸出時。

相關文章