当前位置: 首页 > article >正文

《算法竞赛进阶指南》0x31质数

定义

若一个正整数无法被除了1和它本身之外的任何自然数整除,则称这个数为质数(素数),反之为合数。
对于一个足够大的整数N,不超过N的质数大约有 N/lnN个,分布比较松散。

质数的判定

试除法
若一个正整数N为合数,则存在一个能整除N的数T,其中 2 ≤ T ≤ N 2\leq T \leq \sqrt{N} 2TN

因此,我们只需要扫描 [ 2 , N ] [2,\sqrt{N}] [2,N ]之间的所有整数,一次检查能否整除N,若不能则N是质数,否则N是合数。

bool is_prime(int n)
{
	if (n < 2) return false;
	for (int i = 2; i <= sqrt(n); i ++ )
	if (n % 2 == 0) return false;
	return true;
}

质数的筛选

给定一个整数N,求出[1,n]之间所有的质数,称为质数的筛选问题。

Erathosthenes筛法
Erathosthenes筛法基于这样的思想,任意整数x的倍数都不是质数,我们可以从2开始,从小到大扫描每一个数,把他们的倍数标记为合数。当扫描到一个数时,如果没有被标记,那么就是质数。

void primes(int n)
{
	memset(st, 0, sizeof st);
	for (int i = 2; i <= n; i ++ )
	{
		if (st[i]) continue;
		prime[cnt ++ ] = i;
		for (int j = i; j <= n / i; j ++ ) st[i * j] = 1;
	}
}

时间复杂度为 O ( n l o g l o g n ) = n ∗ ( 1 / 2 + 1 / 3 + 1 / 5 + 1 / 7 + . . . + 1 / n ) = n l o g l o g n 时间复杂度为O(nloglogn)=n*(1/2+1/3+1/5+1/7+...+1/n)=nloglogn 时间复杂度为O(nloglogn)=n(1/2+1/3+1/5+1/7+...+1/n)=nloglogn

线性筛法
线性筛法通过“从大到小累积质因子”的方式标记每个合数。
1.依次考虑[2,n]之间的每一个数i。
2.若st[i]=0,说明i是质数,保存下来。
3.扫描每个质数p,令v[i*p]=p.也就是在i的基础上累计一个质因子p。因为p<=i的最小质因子,所以p就是合数i*p的最小质因子。

void primes(int n)
{
	memset(st, 0, sizeof st);
    cnt = 0;
    for (int i = 2; i <= n; i ++ )
    {
        if (!st[i]) prime[cnt ++ ] = i;
        for (int j = 0; i * prime[j] <= n; j ++ )
        {
            st[i * prime[j]] = true;
            if (i % prime[j] == 0) break;
        }
    }
}

每个合数i*p只会被最小质因子p筛一次,时间复杂度为O(n)。

质因数分解

任何一个大于1的正整数都能唯一分解成有限个质数的乘积,可写作:
N = p 1 c 1 p 2 c 2 . . . p m c m N=p_1^{c1}p_2^{c2}...p_m^{cm} N=p1c1p2c2...pmcm
其中ci为正整数,pi为质数,且 p 1 < p 2 < . . . < p n p_1 < p_2 < ... <pn p1<p2<...<pn

试除法

void divide(int n)
{
	m = 0;
	for (int i = 2; i <= sqrt(n); i ++ )
	{
		if (n % 2 == 0){
			p[ ++ m] = i, c[m] = 0;
			while (n % i == 0) n /= i, c[m] ++ ;
		}
	}
	if (n > 1) p[ ++ m] = 1, c[m] = 1;
	for (int i = 1; i <= m; i ++ )
	cout << p[i] << '^' << c[i] << endl;
}

时间复杂度为 O ( n ) O(\sqrt{n}) O(n )

例题

acwing196.质数距离

如果直接使用线性筛求出[2,r]之间的所有质数的话时间复杂度为T*10^9,显然会超时,我们考虑使用线性筛求出 [ 2 , r ] [2,\sqrt{r}] [2,r ]之间所有的素数,对于每个素数,把[l,r]中能被该质数整除的数标记。

时间复杂度分为两部分,线性筛的时间复杂度加上求区间合数的时间复杂度。
前者时间复杂度为 O ( r ) O(\sqrt{r}) O(r )
后者时间复杂度为 ( r − l ) / 2 + ( r − l ) / 3 + ( r − l ) / 5 + . . . + ( r − l ) / r = O ( ( r − l ) ∗ ( l o g l o g n ) ) (r-l)/2+(r-l)/3+(r-l)/5+...+(r-l)/\sqrt{r}=O((r-l)*(loglog\sqrt{n})) (rl)/2+(rl)/3+(rl)/5+...+(rl)/r =O((rl)(loglogn ))
总和为 O ( T ∗ ( r + ( r − l ) ∗ ( l o g l o g n ) ) ) O(T*(\sqrt{r}+(r-l)*(loglog\sqrt{n}))) O(T(r +(rl)(loglogn )))

#include <iostream>
#include <cstring>
using namespace std;
#define N 1000010
typedef long long ll;
bool st[N];
int prime[N];
int cnt = 0;
void init(int n)
{
    memset(st, 0, sizeof st);
    cnt = 0;
    for (int i = 2; i <= n; i ++ )
    {
        if (!st[i]) prime[cnt ++ ] = i;
        for (int j = 0; i * prime[j] <= n; j ++ )
        {
            st[i * prime[j]] = true;
            if (i % prime[j] == 0) break;
        }
    }
}
int main()
{
    int l, r;
    while (cin >> l >> r)
    {
        init(500000);
        memset(st, 0, sizeof st);
        for (int i = 0; i < cnt; i ++ )
        {
            
            ll p = prime[i];
            for (ll j = max(2 * p, (l - 1 + p) / p * p); j <= r; j += p)
            st[j - l] = true;
        }
        cnt = 0;
        for (int i = 0; i <= r - l; i ++ )
        {
            if (!st[i] && i + l >= 2) prime[cnt ++ ]  = i + l;
        }
        if (cnt < 2) puts("There are no adjacent primes.");
        else{
            int minp = 0, maxp = 0;
            for(int i = 0; i < cnt - 1; i ++ )
            {
                int d = prime[i + 1] - prime[i];
                if (d < prime[minp + 1] - prime[minp]) minp = i;
                if (d > prime[maxp + 1] - prime[maxp]) maxp = i;
            }
            printf("%d,%d are closest, %d,%d are most distant.\n",prime[minp], prime[minp + 1], prime[maxp], prime[maxp + 1]);
        }
    }
    return 0;
}

acwing197.阶乘分解
首先排除先求出阶乘再用试除法分解质因数的方法。
再排除对于1-n每一个整数分解质因数,最后进行整合的方法,因为这样的时间复杂度为 O ( n n ) O(n\sqrt{n}) O(nn ),依然会超时。
考虑枚举质数,因为N!的阶乘的质因数肯定不会大于N,所以找丛2到N的所有质数p。
对于每一个p^k,求其会贡献多少个质数p的数量,即
⌊ N / p ⌋ + ⌊ N / p 2 ⌋ + ⌊ N / p 3 ⌋ + . . . + ⌊ N / p k ⌋ \lfloor N/p \rfloor+\lfloor N/p^2 \rfloor+\lfloor N/p^3 \rfloor+...+\lfloor N/p^k \rfloor N/p+N/p2+N/p3+...+N/pk
一共有 l o g p n log_p^n logpn项,可以以O(1)的时间求出每一项的值。又因为前n个数中质数数量为n/logn,所以 l o g 2 n + l o g 3 n + l o g 5 n . . . < l o g 2 n ∗ n / l o g 2 n = n log_2^n+log_3^n+log_5^n...<log_2^n *n/log_2^n = n log2n+log3n+log5n...<log2nn/log2n=n
所以总体时间复杂度为 O ( n ) O(n) O(n)

#include <iostream>
using namespace std;
#define N 1000010
bool st[N];
int prime[N];
int n, cnt = 0;
typedef long long ll;
void init(int n)
{
    for (int i = 2; i <= n; i ++ )
    {
        if(!st[i]) prime[cnt ++ ] = i;
        for (int j = 0; prime[j] * i <= n; j ++ )
        {
            st[i * prime[j]] =true;
            if (i % prime[j] == 0)break;
        }
    }
}
int main()
{
    cin >> n;
    init(n);
    for (int i = 0; i < cnt; i ++ )
    {
        int p = prime[i];
        int s = 0;
        for (ll j = p; j <= n; j *= p)
        s += n / j;
        printf("%d %d\n", p, s);
    }
    return 0;
}

http://www.kler.cn/a/281230.html

相关文章:

  • AI写作使用技巧分享 关于我用AI提示词的三大妙招
  • 软件运维实施维保方案(Doc完整版原件)
  • 重卡智能充电机器人
  • 华为AC旁挂二层组网配置详解:从DHCP部署到无线业务配置,完成网络搭建
  • Lama:基于傅立叶卷积的分辨率鲁棒性大掩模修复
  • ai绘画comfyUI专栏介绍
  • <Rust>egui学习之小部件(三):如何为窗口UI元件设置布局(间隔、水平、垂直排列)?
  • 【CVPR‘24】DeCoTR:使用 2D 和 3D 注意力增强深度补全
  • 96.不同的二叉搜索树
  • Android 动态性能框架 (ADPF)
  • MySQL:SQL调优的简单实践
  • Vue——初识vue
  • git分支
  • Java正则表达式和枚举(Enum)
  • 华为OD 机器人搬砖 二分法 思路
  • Leetcode 18-四数之和
  • 体育数据API纳米足球数据API:足球数据接口文档API示例④
  • 虚幻引擎(Unreal Engine)技术使得《黑神话悟空传》大火,现在重视C++的开始吃香了,JAVA,Go,Unity都不能和C++相媲美!
  • 机器学习之监督学习(一)
  • Nuclei:开源漏洞扫描器