质数相关问题

1.素数判定 (试除法)

(1)最简单的暴力版本 O(n)
bool is_prime(int n)
{
	if(n<2)
	return false;
	for(int i=2;i<n;i++)
		if(n%i==0)
		return false;
	return true;
}
(2)由“因子都是成对出现的”进行到√n的优化 O(√n)

d ≤ n d d≤\frac{n}{d} ddn d ≤ n d≤\sqrt{n} dn

判断条件不推荐写成i*i≤n,在n接近于int最大值时,i2可能<n,但(i+1)2可能就会溢出了,i可能会变成一个负数就会影响循环的终止
也不推荐写成i≤sqrt(n),这样会多次调用sqrt()影响效率

优化方法:上述判断条件改为i≤n/i注意:这里取≤

(3)应用素数的性质进行加速

加速技巧大于4的素数总是等于6x+1 或6x-1

bool isPrime(int num)
{
	if(num<=3)
	return num>1;
	
	//排除掉非 6x-1和6x+1的数
	if(num%6!=1&&num%6!=5) 
	return false;
	//从符合6x-1和6x+1形式的数中在进行筛除
	int cnt=(int)sqrt(num);
	for(int i=5;i<=cnt;i+=6)
	{
		if(num%i==0||num%(i+2)==0)//这里%i和%(i+2)  类似于表示5 和7 这样的倍数
					//由上知素数必然是6x-1或6x+1形式,然后再试除,排除有其他因子的
		return false;
	}
	return true;
}

2.分解质因数 (试除法)

唯一分解定理(算数基本定理): 任一大于 1的 自然数 𝑁,都可以唯一分解为有限个素数之积: N = P 1 c 1 P 2 c 2 . . . P r c r N=P_{1}^{c_1}P_{2}^{c_2}...P_{r}^{c_r} N=P1c1P2c2...Prcr
且最多只有一个大于√n的质因子Pi,因为一旦出现两个,相乘就会大于n,所以这里最后要有一个判断,如果≤√n部分除完,n仍>1,则说明是那个>√n的质因子,直接输出。

例题

在这里插入图片描述

输入
2
6
8
输出
2 1
3 1

2 3

代码 O(logn~√n)
当n=2k时,枚举到2就可以把n除尽,所以时间复杂度为logn
#include <iostream>
using namespace std;
void divide(int n)
{
    for(int i=2;i<=n/i;i++)//这里的n在变,但是i不用循环到初始x的平方根,只用循环到剩余x的平方根,如果i超过剩余x的平方根,那么就说明剩余x是一个质数,就不需要再循环了。
    {    
        if(n%i==0)//i一定会是质数
         {
            int cnt=0;
            while(n%i==0)
            {
                n/=i;
                cnt++;
            }
            cout<<i<<" "<<cnt<<endl;
         }
    }
    if(n>1)
    cout<<n<<" 1"<<endl;
    cout<<endl;
}
int main()
{
    int n;
    cin>>n;
    while(n--)
    {
        int x;
        cin>>x;
        divide(x);
    }
    return 0;
}

3.素数筛法

(1)朴素筛法 O(nlogn)  156ms
int primes[maxn];
bool st[maxn];
int cnt;
void get_primes(int n)
{
    for(int i=2;i<=n;i++)
    {
        if(!st[i])//如果没有被筛
        primes[cnt++]=i;

        for(int j=i+i;j<=n;j+=i)//用2~n所有的数取筛,可能会有很多重复筛
            st[j]=true;
    }
}
复杂度分析:

       当i=2时,内循环了 n 2 \frac{n}{2} 2n次,i=3时,循环了 n 3 \frac{n}{3} 3n次…

       总次数: n 2 + n 3 + . . . + n n = n ∗ ( 1 2 + 1 3 + . . . + 1 n ) \frac{n}{2}+\frac{n}{3}+...+\frac{n}{n}=n*(\frac{1}{2}+\frac{1}{3}+...+\frac{1}{n}) 2n+3n+...+nn=n(21+31+...+n1)

       又因为 1 2 + 1 3 + . . . + 1 n \frac{1}{2}+\frac{1}{3}+...+\frac{1}{n} 21+31+...+n1是调和级数,~ l n n ( = log ⁡ e n ) ln n(=\log_en) lnn(=logen)

       所以时间复杂度为 O ( n l o g n ) O(nlogn) O(nlogn) ( n log ⁡ e n < n log ⁡ 2 n ) (n\log_en<n\log_2n) (nlogen<nlog2n)

(2)埃式筛法 O(nlog log n)  52ms

算法思想:

  • 初始数列 :{2 、3,4,5,6,7,8,9,10 ,11 ,12 ,13 ,… ,n}

  • 输出 最小的素数 {2} ,筛掉 2的倍数,剩下 {3 ,5,7,9,11 ,13 ,…}

  • 输出 素数 {2,3} ,筛掉 3的倍数,剩下 {5 ,7,11 ,13 ,…}

  • 输出 素数 {2,3,5} {2,3,5} ,筛掉 5的倍数,剩下 {7 ,11 、13 ,…}

  • 继续以上步骤, 直到 n。

void get_primes(int n)
{
    for(int i=2;i<=n;i++)
    {
        if(st[i])
        continue;

        primes[cnt++]=i;
        for(int j=i+i;j<=n;j+=i)//每次只筛掉质数相应的倍数(一个简单小优化)
        st[j]=true;
    }
}
时间复杂度:O(nlog log n)

筛选次数:n/2+n/3+…+n/n

调和级数: Σ i = 1 n 1 i = ln ⁡   n + γ + ε n \varSigma _{i=1}^{n}\frac{1}{i}=\ln\text{ }n+\gamma +\varepsilon _n Σi=1ni1=ln n+γ+εn ( c = γ + ε n c=\gamma +\varepsilon _n c=γ+εn,常数)

素数分布: π ( x ) = x ln ⁡ x \pi \left( x \right) =\frac{x}{\ln x} π(x)=lnxx,所以1~n中一共会有 n ln ⁡ n \frac{n}{\ln n} lnnn个质数

这里我们只要求1~n中所有质数的调和级数

粗略估计 n ln ⁡ n ∗ ( 1 2 + 1 3 + . . . + 1 n ) \frac{n}{\ln n}*(\frac{1}{2}+\frac{1}{3}+...+\frac{1}{n}) lnnn(21+31+...+n1) ~ O ( n ln ⁡ n ∗ ln ⁡ n ) O(\frac{n}{\ln n}*\ln n) O(lnnnlnn) ~ O ( n ) O(n) O(n)

eg.  n=232 l o g n = 32 logn=32 logn=32 l o g l o g n = 5 loglogn=5 loglogn=5,所以loglogn是可看作常数的

精确估计:O(nloglogn)
在这里插入图片描述
在这里插入图片描述

参考链接:完整质数朴素筛法与埃氏筛法复杂度的证明

(3)线性筛法 (欧拉筛法) O(n)  33ms 略快一些

每个合数仅被它最小的质因数筛去

void get_primes(int n)
{
	//fill(st,st+maxn,false); 这个初始化会比较耗时间,但是好像去掉也可以
	cnt=0;
	for(int i=2;i<=n;i++)
	{
		if(!st[i])//没有被筛过
		primes[cnt++]=i;
		for(int j=0;j<cnt&&i*primes[j]<=n;j++)//primes[j]<=n/i从小到大枚举所有质数
		{
			st[i*primes[j]]=true;
			if(i%primes[j]==0)//i是某个质数的倍数,就break掉,保证只被最小的质数筛一次 
			break;			//primes[j]一定是最小质因子,因为从小到大枚举且第一次出现
		}	
	}	 
}

当n=1e6时,线性筛法和埃式筛法差不多,但当n=1e7时,线性筛法会比埃式筛法快一倍

在内循环中有两种情况:

1.i%primes[j]==0 说明primes[j]一定是最小质因子,因为是从小到大枚举且第一次出现,且primes[j]一定是i*primes[j]的最小质因子,所以st[i*primes[j]]=true成立

2.i%primes[j]!=0 说明当前枚举到的所有质数primes[j]都<i的所有质因子,primes[j]也一定是i*primes[j]的最小质因子(乘号左右两部分中最小的质因子是prime[j])

综上,不论什么情况,primes[j]都一定是i*primes[j]的最小质因子

每一个合数都一定会被筛去

对于一个合数,所以其最小质因子一定是存在的。假设primes[j]是x的最小质因子,当i枚举到x/primes[j]时,就会被筛掉的。

线性分析:每一个数都只会有一个最小质因子,所以每一个数至多只会被筛一次,所以是线性的。

对于内循环判断条件j<cnt&&i*primes[j]<=n

j<cnt&&可以不写:如果i是合数的话,当primes[j]枚举到i的最小质因子的时候就一定会停下来(i的最小质因子一定<i),当i是质数时,当primes[j]==i时,也会停下来。所以无论如何j一定会在<cnt时停下来。

i*primes[j]<=n含义:我们只需要筛选出n以内的质数,所以当乘积大于n的时候就可以停止了.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值