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} d≤dn, d ≤ n d≤\sqrt{n} d≤n
判断条件不推荐写成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(lnnn∗lnn) ~
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的时候就可以停止了.