有关素数的算法

目录

  • 一、素性判断
  • 二、埃氏筛法
    • 2.1 问题描述
    • 2.2 问题简析
    • 2.3 代码
  • 三、区间筛法
    • 3.1 问题描述
    • 3.2 问题简析
    • 3.3 代码

一、素性判断

素数,又叫质数,是指一个整数,除了1和本身之外,还有其它的因数(注意:1不是素数)。因此,对于一个整数 n n n,我们只要检测 [ 2 , n − 1 ] [2, n - 1] [2,n1] 能否整除 n n n
整除的定义: ∃ \exist a , b , k ∈ Z a, b, k \in \mathbb{Z} a,b,kZ,使得 a = k b a = kb a=kb,则称 b b b 整除 a a a,记 b b b ∣ | a a a
d d d n n n 的因数,则 n d \frac{n}{d} dn 也是 n n n 的因数。由 n = d ∗ n d n = d \ast \frac{n}{d} n=ddn 可知, m i n ( d , n d ) ≤ n min(d, \frac{n}{d}) \leq \sqrt{n} min(d,dn)n 。同时,我们知道了一个因数,就能求出另一个因数。所以,我们只要检测 [ 2 , n ] [2, \sqrt{n}] [2,n ] 即可。

  • 判断一个正整数的素性
// 检测n是否是素数
bool if_prime(int n)
{
    // 遍历 [2, sqrt{n}],素数要忽略1和本身
    for (int i = 2; i * i <= n; i++)
    {
        if (n % i == 0)
            return false;
    }
    return n != 1;        // 1 不是素数
}

该算法的核心思想是判断因数。以下两个算法也用到了该思想:

  • 枚举一个正整数的所有因数
// 枚举n的因数
vector<int> divisors(int n)
{
    vector<int> ans;
    // 因数要包含1和本身
    for (int i = 1; i * i <= n; i++)
    {
        if (n % i == 0)
            ans.push_back(i);
        if (i != n / i)             // i*i==n时,要忽略一个i
            ans.push_back(n / i);
    }
    return ans;
}
  • 分解一个正整数
// 将整数n分解成若干个素数,除1和本身
map<int, int> factors(int n)
{
    map<int, int> ans;      // 分解n后,有ans[i]个i
    // n==1,特殊考虑
    if (n == 1)
    {
        ans[n] = 1;
        return ans;
    }
    // 1和本身总是因数,这里忽略
    for (int i = 2; i * i <= n; i++)
    {
        // 可能有若干个i
        while (n % i == 0)
        {
            ans[i]++;       // 分解出一个i
            n /= i;
        }
    }
    if (n != 1)             // n==1,已经分解完了
        ans[n] = 1;
    return ans;
}

二、埃氏筛法

2.1 问题描述

给定正整数 n n n,求出 n n n 以内有多少个素数。

2.2 问题简析

解决该问题需要用到埃氏筛法:先将 [ 2 , n ] [2, n] [2,n] 内的整数写下来。接下来,开始筛数。每次筛数,保留最小的数 m m m,即素数,然后筛去 m m m 的倍数。经过多轮的筛数,留下的就都是素数了。

2.3 代码

#include 

using namespace std;

#define MAX 10000000

int n;
vector<int> ans;
bool is_prime[MAX];

int main()
{
	scanf("%d", &n);
	fill(begin(is_prime), end(is_prime), true);
	for (int i = 2; i <= n; i++)
	{
		if (is_prime[i])
		{
			ans.push_back(i);

			// 筛去 i 的倍数
			for (int j = 2 * i; j <= n; j += i)
				is_prime[j] = false;
		}
	}

	printf("%d\n", ans.size());

	return 0;
}

三、区间筛法

3.1 问题描述

给定正整数 a a a b b b,求除 [ a , b ) [a, b) [a,b) 内的素数个数。

3.2 问题简析

由上文可知, b b b 的最小质因数不大于 b \sqrt{b} b 。所以,用 [ 2 , b ) [2, \sqrt{b}) [2,b ) 就能筛去 [ a , b ) [a, b) [a,b) 里的数。解释:
∃   m ∈ [ 2 , b ) ,使得  m   ∣   b 。假设  m  是素数, 则遍历到  b  之前,就能筛去  b 。 \begin{split} &\exist~m \in [2, \sqrt{b}),使得~m~|~b。假设~m~是素数,\\ &则遍历到~\sqrt{b}~之前,就能筛去~b。 \end{split}  m[2,b ),使得 m  b。假设 m 是素数,则遍历到 b  之前,就能筛去 b

3.3 代码

#include 

using namespace std;

#define MAX 1000003

typedef long long ll;

ll a, b;
bool is_prime_mini[MAX];      // [2, sqrt(b))
bool is_prime[MAX];           // [a, b)

int main()
{
	scanf("%lld%lld", &a, &b);

	fill(begin(is_prime_mini), end(is_prime_mini), true);
	fill(begin(is_prime), end(is_prime), true);

	// 筛[2, sqrt(b))
	for (int i = 2; (ll)i * i < b; i++)
	{
		if (is_prime_mini[i])
		{	// 筛[2, sqrt(b))
			for (int j = 2 * i; (ll)j * j < b; j += i)
				is_prime_mini[j] = false;

			// 筛[a, b)
			ll j = a / i;                 // 向下取整,可能差一个i
			if (j * i < a)    j += 1;     // 差一个i
			for (j *= i; j < b; j += i)
				is_prime[j - a] = false;  // [a, b) 压缩到 [0, b - a)
		}
	}

	int ans = 0;
	for (ll i = a; i < b; i++)
		if (is_prime[i - a])
			ans++;
	printf("%d\n", ans);

	return 0;
}

该算法有几个注意点:

  • 1、一般区间筛法中 a a a b b b 都是较大的,所以要用 long long 来存储。
  • 2、区间一般默认左闭右开,所以两个循环判断条件都没有等号。
  • 3、筛完 [ 2 , b ) [2, \sqrt{b}) [2,b ) 后,再筛 [ a , b ) [a, b) [a,b),需要注意 j 的取值。 j 应该为 i 的倍数,且 j 应该不小于 a a a,所以要先比较 j a a a 的大小。

你可能感兴趣的:(algorithms,math,算法)