质数算法总结

学习视频

参考李永乐老师的数学教程

艾尔尼筛法

朴素解法

2是质数,2的倍数全部都不是质数,把不是质数的数在地图中全部涂掉

这个做法 的复杂度很高,是 $o(nlog(logN))$

1
2
3
4
5
6
7
8
bool isnp[MAXN]; // is not prime: 不是素数
void init(int n)
{
    for (int i = 2; i * i <= n; i++)
        if (!isnp[i])
            for (int j = i * i; j <= n; j += i)
                isnp[j] = 1;
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
#include <bits/stdc++.h>
using namespace std;
 
const long long maxn = 10000007 + 10;
const long long maxp = 700000;
int vis[maxn];    // i是合数vis为1,i是素数,vis为0
long long prime[maxp];
//筛选
void sieve(long long n){
    //取根号
    long long m = (long long)sqrt(n + 0.5);
    memset(vis, 0, sizeof(vis));
    vis[2] = 0;//地图
    for (long long i = 3; i <= m; i += 2) {
        if(!vis[i])
            for (long long j = i * i; j <= n; j += i)
                vis[j] = 1;
        if(i * i > n)
            break;
    }
}
 
long long gen(long long n){
    sieve(n);
    long long c = 1;
    prime[0] = 2;
    for (long long i = 3; i <= n; i += 2)
        if(!vis[i]) //被涂黑的不是质数
            prime[c++] = i;
    return c;
}
 
int main()
{
    long long n, c;
    cout << "刷素数到n:";
    cin >> n;
    c = gen(n);
    for (long long i = 0; i < c; i++)
        printf("%lld", prime[i]);
    cout << endl << c;
    return 0;
}

费马小定理优化 【科普,不用它】

这个算法不用,只是做个科普

定理内容: $a^p-a$ 是 a 的倍数,即 $(a^p-a) \mod p=0$

a=?, p=5 1 2 3 4
$a^p-a$ 0 30 240 1020
$\mod p$ 0 0 0 0

例如: $$ 令 \space a=1\ P=5\ 可得到:\ (a^P-a) \mod P \= 0 \mod 5 \= 0 $$ 以上定理无用,因为有卡迈克尔数,比如 561

线性筛法

证明 这是一个 $O(N)$ 的算法

i 的值 质数表 筛去的数
2 2 4
3 2, 3 6, 9
4 2, 3 8
5 2, 3, 5 10, 15, 25
6 2, 3, 5 12
7 2, 3, 5, 7 14, 21, 28, 35
$⋯ $
每个质数只被筛一次,复杂度变为 $O(n)$ ,可以AC。

参考博客

这个算法的时间复杂度是 $o(N)$

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
bool isnp[MAXN];//是否是合数
vector<int> primes; // 质数表
void init(int n)
{
    for (int i = 2; i <= n; i++)
    {
        if (!isnp[i]) //如果不是合数
            primes.push_back(i);
        for (int p : primes)
        {
            if (p * i > n)
                break;//越界就跳过
            isnp[p * i] = 1;//可以直接乘出的数是合数
            if (i % p == 0)
                break; //直接break,比如 x*p ==i , 前面可以直接用 x乘出来
        }
    }
}

参考洛谷的题解

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
#include <cstdio>
#include <cstring>

bool isPrime[100000010];
//isPrime[i] == 1表示:i是素数
int Prime[6000010], cnt = 0;
//Prime存质数

void GetPrime(int n)//筛到n
{
	memset(isPrime, 1, sizeof(isPrime));
	//以“每个数都是素数”为初始状态,逐个删去
	isPrime[1] = 0;//1不是素数
	
	for(int i = 2; i <= n; i++)
	{
		if(isPrime[i])//没筛掉 
			Prime[++cnt] = i; //i成为下一个素数
			
		for(int j = 1; j <= cnt && i*Prime[j] <= n/*不超上限*/; j++) 
		{
        	//从Prime[1],即最小质数2开始,逐个枚举已知的质数,并期望Prime[j]是(i*Prime[j])的最小质因数
            //当然,i肯定比Prime[j]大,因为Prime[j]是在i之前得出的
			isPrime[i*Prime[j]] = 0;
            
			if(i % Prime[j] == 0)//i中也含有Prime[j]这个因子
				break; //重要步骤。见原理
		}
	}
}

int main()
{
	int n, q;
	scanf("%d %d", &n, &q);
	GetPrime(n);
	while (q--)
	{
		int k;
		scanf("%d", &k);
		printf("%d\n", Prime[k]);
	}
	return 0;
}