NO98十六届蓝桥杯备战|数论板块-质数|质数和合数|试除法|质数筛|埃氏筛|线性筛|线性筛素数|素数密度|Goldbach‘s Conjecture(C++)

质数和合数
  • ⼀个⼤于1的⾃然数,除了1和它⾃⾝外,不能被其他⾃然数整除的数叫做质数;否则称为合数。其中,质数⼜称素数。
    规定1 既不是质数也不是合数。
试除法判断质数
  • 对于⼀个数x ,根据定义,可以从[2, x - 1]⼀个⼀个尝试,判断x 能否被整除。
    但是,没有必要每⼀个都去判断。因为a如果是x的约数,那么x/a也是x的约数。
    因此,我们仅需判断较⼩的a是否是x的约数,没有必要再去看看x/a 。
    那么,仅需枚举到 x \sqrt{ x } x 即可。
bool isprime(int x)  
{  
	if(x <= 1) return false; // ⼩于等于 1 的数不考虑 
	 
	// 试除法判断是否是质数 - 只需枚举到 sqrt(x)  
	for(int i = 2; i <= x / i; i++) // 防溢出的写法  
	{  
		if(x % i == 0) return false;  
	}  
	return true;  
}

时间复杂度:
枚举到 n \sqrt{ n } n ,因此时间复杂度为O( n \sqrt{ n } n )

P5736 【深基7.例2】质数筛 - 洛谷

读⼀个判断⼀个即可

#include <bits/stdc++.h>
using namespace std;

bool isprime(int x)
{
    if (x <= 1) return false;

    for (int i = 2; i <= x / i; i++)
    {
        if (x % i == 0) return false;
    }
    return true;
}

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);

    int n; cin >> n;
    while (n--)
    {
        int x; cin >> x;
        if (isprime(x)) cout << x << " ";
    }
    
    return 0;
}

筛质数

引⼊

上⼀个专题学习了如何判断⼀个数是否是质数,如果此时想知道[1, n]中有多少个素数呢?或者是[1, n]中的素数⾥⾯,第k个素数是多少?

  • ⼀个⾃然的想法就是从2 开始,依次向后对每⼀个⾃然数进⾏⼀次质数检验。
    但是这种解法相对暴⼒,我们这⾥介绍两种⽅法,能够快速地将[1, n] 中的素数全部记录下来。
埃⽒筛法

算法思想:

  • 对于任意⼀个⼤于1 的正整数,那么它的k(k > 1) 倍就是合数。
    因此,如果我们从⼩到⼤考虑每个数,然后同时把当前这个数的所有倍数记为合数,没有被标记的数就是素数。
    ⼩优化:
  • 找到⼀个质数x 之后,可以从该数的x 倍向后筛,因此⼩于x 的倍数⼀定被之前筛过了
bool st[N]; // 当前这个数有没有被筛掉  
int p[N]; // 记录质数
int cnt; // 统计质数个数  

// 埃⽒筛  
void get_prime()  
{  
	for(int i = 2; i <= n; i++)  
	{  
		if(!st[i]) // 没有被标记,说明是质数  
		{  
			p[++cnt] = i; // 记录这个质数  
			// 从 i*i 开始,因为⼩于 i 的倍数已经被划掉了  
			for(LL j = i * i; j <= n; j += i) // 筛掉这个质数的倍数  
			{  
				st[j] = true;  
			}  
		}  
	}  
}

时间复杂度:
埃⽒筛的时间复杂度为:O(n log n) 。
关于时间复杂度,⽹上各个地⽅都有详细的证明。因为很⿇烦,⽽且涉及积分的知识,这⾥就不再赘述。

线性筛法

线性筛法,⼜称欧拉筛法。算法思想:

  • 在埃⽒筛法中,它会将⼀个合数重复多次标记。如果能让每个合数都只被标记⼀次,那么时间复杂度就可以降到O(n) 了。
    我们的做法是,让每⼀个合数被它的最⼩质因数筛掉
    从前往后遍历每一个数i
  1. 用i的质数倍去筛
  2. 如果i是质数的倍数时,停止
int n, q;  
bool st[N];  
int p[N], cnt;  
// 欧拉筛  
void get_prime()
{  
	for(int i = 2; i <= n; i++)  
	{  
		if(!st[i]) p[++cnt] = i; // 如果没标记过,就是质数  
		// 枚举所有的质数  
		for(int j = 1; 1ll * i * p[j] <= n; j++)  
		{  
			st[i * p[j]] = true;  
			if(i % p[j] == 0) break;  
			/*  
			这个判定条件能让每⼀个合数被⾃⼰的最⼩质因数筛掉。  
			1. 如果 i 是合数,枚举到最⼩质因数的时候跳出循环  
			2. 如果 i 是质数,枚举到⾃⾝时跳出循环  
			注意,在筛的过程中,我们还能知道 p[j] 是 i 的最⼩质因数  
			*/  
		}  
	}  
}

时间复杂度:
每个数只会被⾃⾝最⼩的质因数筛掉⼀次,时间复杂度为O(n)

P3383 【模板】线性筛素数 - 洛谷

埃⽒筛

#include <bits/stdc++.h>
using namespace std;

typedef long long LL;

const int N = 1e8 + 10;

int n, q;
bool st[N];
int p[N], cnt;

void get_prime()
{
    for (LL i = 2; i <= n; i++)
    {
        if (!st[i])
        {
            p[++cnt] = i;
            for (LL j = i * i; j <= n; j += i)
            {
                st[j] = true;        
            }
        }
    }
}

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);

    cin >> n >> q;

    get_prime();

    while (q--)
    {
        int k; cin >> k;
        cout << p[k] << endl;
    }
    
    return 0;
}

线性筛

#include <bits/stdc++.h>
using namespace std;

typedef long long LL;

const int N = 1e8 + 10;

int n, q;
bool st[N];
int p[N], cnt;

void get_prime()
{
    for (LL i = 2; i <= n; i++)
    {
        if (!st[i]) p[++cnt] = i;

        for (int j = 1; i * p[j] <= n; j++)
        {
            st[i * p[j]] = true;
            if (i % p[j] == 0) break;
        }
    }
}

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);

    cin >> n >> q;

    get_prime();

    while (q--)
    {
        int k; cin >> k;
        cout << p[k] << endl;
    }
    
    return 0;
}
P1835 素数密度 - 洛谷
  • 埃⽒筛+线性筛。
    先筛出[1, r]之间的质数,然后⽤这些质数去筛[l, r]区间的质数。
#include <bits/stdc++.h>
using namespace std;

typedef long long LL;

const int N = 1e6 + 10;

int l, r;
bool st[N];
int p[N], cnt;

bool ret[N];

void get_prime()
{
    int n = sqrt(r);
    for (int i = 2; i <= n; i++)
    {
        if (!st[i]) p[++cnt] = i;

        for (int j = 1; 1ll * i * p[j] <= n; j++)
        {
            st[i * p[j]] = true;
            if (i % p[j] == 0) break;
        }
    }
}

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);

    cin >> l >> r;
    get_prime();

    l  = l == 1 ? 2 : l;

    for (int i = 1; i <= cnt; i++)
    {
        LL x = p[i];
        for (LL j = max(x * 2, (x + l - 1) / x * x); j <= r; j += x)
        {
            ret[j - l] = true;        
        }
    }
    int sum = 0;
    for (int i = l; i <= r; i++)
    {
        if (!ret[i - l]) sum++;
    }
    cout << sum << endl;
    
    return 0;
}
UVA543 Goldbach’s Conjecture - 洛谷

先筛⼀下质数,然后在筛出来的质数中,找出两个数的和等于给定的数

#include <bits/stdc++.h>
using namespace std;

const int N = 1e6 + 10;

bool st[N];
int p[N], cnt;

void get_prime()
{
    int n = 1e6;
    for (int i = 2; i <= n; i++)
    {
        if (!st[i]) p[++cnt] = i;

        for (int j = 1; 1ll * i * p[j] <= n; j++)
        {
            st[i * p[j]] = true;
            if (i % p[j] == 0) break;
        }
    }
}

void solve(int x)
{
    for (int i = 2; i <= cnt; i++)
    {
        if (!st[x - p[i]])
        {
            printf("%d = %d + %d\n", x, p[i], x-p[i]);
            break;
        }
    }
}

int main()
{
    get_prime();

    int x;
    while (cin >> x, x)
    {
        solve(x);
    }

    return 0;
}
<think>好的,我现在需要帮用户介绍一下素数法,特别是埃氏筛线性筛法,并且要给出C++代码。首先,用户可能是在学习算法或者数论相关内容,需要了解这两种法的原理、步骤以及代码实现。用户可能对它们的区别各自的优缺点感兴趣,尤其是时间复杂度效率方面。 首先,我应该回忆埃氏筛法的基本思想。拉托斯特尼法是通过不断标记素数的倍数来选出所有素数。它的时间复杂度是O(n log log n),对于一般的问题已经够用了,但可能会有重复标记的情况,比如数字6会被23都标记一次,导致效率稍低。 接下来是线性筛法,也就是欧拉。这种法的核心是每个合数只被它的最小质因数掉,这样每个合数只被标记一次,时间复杂度是O(n),但实现起来稍微复杂一些,需要维护一个质数列表,并且每次用当前质数去乘已经找到的质数,直到当前数能被该质数整除为止。 用户可能需要对比这两种方法的优缺点,比如埃氏筛法虽然时间复杂度稍高,但实现简单,适合小规模数据或者对代码简洁性要求高的场景;而线性筛法在数据规模较大时更高效,但代码稍微复杂,需要更多的空间来存储质数列表。 接下来要考虑用户提供的C++代码示例。需要确保代码正确,并且有注释说明关键步骤。例如,埃氏筛法需要一个布尔数组isPrime,初始化为true,然后从2开始,将每个素数的倍数标记为false。而线性筛法需要维护一个vector存储素数,内层循环中,对于每个数i,用已有的素数prime[j]去标记i*prime[j],并且在i能整除prime[j]时跳出循环,避免重复标记。 还要注意代码的效率优化,比如埃氏筛法可以从i*i开始标记,因为之前的倍数已经被更小的素数标记过了。而线性筛法则必须确保每个合数只被一次,因此需要及时break。 用户可能的疑问包括:为什么线性筛法能保证每个合数只被一次?这时候需要解释,每个合数都是被其最小质因数掉的,比如12会被2掉(当i=6,prime[j]=2时),而不会在i=4,prime[j]=3的时候被,因为此时i%prime[j]等于0,会break,避免用更大的质数。 另外,可能需要说明这两种法的适用场景。例如,埃氏筛法适合需要快速实现且处理数据规模不是特别大的情况,而线性筛法在处理大规模数据时更高效,尤其是在n很大的时候。 最后,用户可能需要实际运行这些代码,所以需要确保代码的正确性,没有语法错误,并且给出测试示例,比如n=30时输出的素数列表是否正确。在代码示例中,最好也包含输入的提示输出的格式,方便用户理解使用。</think>以下是关于**素数法(埃氏筛法与线性筛法)**的详细解析,包含C++代码实现: --- ### 一、拉托斯特尼法(埃氏筛法) #### 核心思想 通过**标记素数的倍数**来选出所有素数,时间复杂度为$O(n \log \log n)$。 #### 步骤 1. 初始化一个布尔数组`isPrime[0..n]`,默认全为`true`。 2. 从最小的素数2开始,若`isPrime[i]`为`true`,则标记所有$i$的倍数为非素数。 3. 重复步骤2直到遍历完$\sqrt{n}$。 #### C++代码 ```cpp #include <iostream> #include <vector> using namespace std; void eratosthenes(int n) { vector<bool> isPrime(n + 1, true); isPrime[0] = isPrime[1] = false; // 01不是素数 for (int i = 2; i * i <= n; ++i) { if (isPrime[i]) { // 从i*i开始标记,因为更小的倍数已被更小的素数标记过 for (int j = i * i; j <= n; j += i) { isPrime[j] = false; } } } // 输出所有素数 for (int i = 2; i <= n; ++i) { if (isPrime[i]) cout << i << " "; } } int main() { int n = 30; cout << "埃氏筛法结果(n=30):"; eratosthenes(n); return 0; } ``` #### 输出 ``` 埃氏筛法结果(n=30):2 3 5 7 11 13 17 19 23 29 ``` --- ### 二、线性筛法(欧拉法) #### 核心思想 确保每个合数**仅被其最小质因数除**,时间复杂度$O(n)$,空间复杂度$O(n)$。 #### 步骤 1. 维护一个质数列表`primes`一个布尔数组`isPrime[0..n]`。 2. 遍历每个数$i$,若未被标记为非素数,则将其加入质数列表。 3. 用当前质数$p$标记$i \times p$为非素数,当$i \bmod p = 0$时终止标记。 #### C++代码 ```cpp #include <iostream> #include <vector> using namespace std; void eulerSieve(int n) { vector<bool> isPrime(n + 1, true); vector<int> primes; // 存储所有素数 isPrime[0] = isPrime[1] = false; for (int i = 2; i <= n; ++i) { if (isPrime[i]) { primes.push_back(i); // i是素数,加入列表 } // 用当前已知素数标记i*primes[j] for (int j = 0; j < primes.size() && i * primes[j] <= n; ++j) { isPrime[i * primes[j]] = false; if (i % primes[j] == 0) break; // 关键:保证只被最小质因数一次 } } // 输出结果 for (int p : primes) cout << p << " "; } int main() { int n = 30; cout << "线性筛法结果(n=30):"; eulerSieve(n); return 0; } ``` #### 输出 ``` 线性筛法结果(n=30):2 3 5 7 11 13 17 19 23 29 ``` --- ### 三、两种法的对比 | 特性 | 埃氏筛| 线性筛| |--------------------|---------------------------------|---------------------------------| | **时间复杂度** | $O(n \log \log n)$ | $O(n)$ | | **空间复杂度** | $O(n)$ | $O(n)$ | | **重复标记次数** | 较多(如6会被23各标记一次) | 无(每个合数仅被标记一次) | | **适用场景** | 小规模数据或快速实现 | 大规模数据或严格时间复杂度要求 | --- ### 四、关键点解析 1. **埃氏筛法的优化**: - 标记起点从$i^2$开始,避免重复标记(如$i=5$时,直接从25开始标记,跳过10、15、20等已被标记的数)。 2. **线性筛法的核心**: - **`if (i % primes[j] == 0) break`**: 假设当前数为$i=4$,质数列表为$[2,3]$,当标记$4 \times 2=8$后,由于$4 \bmod 2=0$,停止标记,避免后续标记$4 \times 3=12$(12会被$6 \times 2=12$除,保证最小质因数优先)。 --- ### 五、实际应用 - **埃氏筛法**:适合一次性生成素数表(如LeetCode计数问题)。 - **线性筛法**:在需要频繁查询或处理极大范围(如$n \geq 10^7$)时更高效。 若有疑问(如代码细节或数学证明),欢迎进一步讨论!
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值