质数
HopeAndLizz · · 个人记录
质数
定义
质数又称素数。
一个大于1的自然数,除了1和它自身外,不能被其他自然数整除的数叫做质数;否则称为合数。
质数数分布定理
在整个自然数集合中,质数的数量不多,分布比较稀疏,对于一个足够大的整数N,不超过N的质数大约有 N/lnN 个,即每lnN个数中有一个质数。
也就是说,对于正整数n,定义
算术基本定理
任何一个大于1的正整数都能唯一分解成有限个质数的乘积,可写作:
其中
并且可以证明:
正约数个数定理
按上述分解质因式后可得
其中包括1和A这两个约数
约数和定理
按上述分解质因式后可得
判断质数
有一个简单而又暴力的算法,即试除法, 即扫描
bool is_prime(int n)
{
if(n < 2) return false;
for(int i = 2;i <= sqrt(n);i++) //i*i<=n
if(n%i == 0) return false;
return true;
}
筛法
筛法是一种高效的构造素数表的算法。
- 先假定所有数字都是质数
- 一个数的倍数(>=2倍)一定不是质数,标记为合数
- 最后没有被标记的,就是质数,因为找不到一个数i使得数i的倍数为这个数(2..i-1找不到它的约数)
埃氏筛法
Eratothenes----埃拉托色尼斯筛法
因为小于
复杂度
void get_prime1()
{
memset(v,0,sizeof(v)); //质数标记
int tot=0;
long long i,j;
for(i=2;i<=n;i++)
{
if(v[i]) continue; //优化1
prime[tot++]=i;
for(j=i*i;j<=n;j=j+i) //优化2
v[j]=1;
}}
线筛
埃氏筛法过程中,许多数据还是被不同的质数重复筛,可以继续进行优化。比如12=3*4=2*6
优化的方法:给每个数确定一种唯一筛法(给每个数确定一种唯一的产生方式)
线性筛法通过从大到小累积质因子的方式标记每个合数。 让每个合数被他的最小质因子筛掉。
欧拉筛code:
int m;//总共质数个数
int v[MAXN];//最小质因子
int prime[MAXN];//质数合集
inline void getpri()
{
for(int i=2;i<=n;i++)
{
if(v[i]==0)//如果之前都没被标记,就是质数
{
v[i]=i;
prime[++m]=i;
}
for(int j=1;j<=m;j++)//枚举已获得的质数
{
int p=prime[j];
if(p>v[i]||p*i>n) break;
//p>v[i]是为了只让最小质因子标记合数,如果删掉还是会重复筛
v[i*p]=p;
}
}
}
区间筛法
题目1:素数密度
题目2:Prime Distance
思路:因为R过大,用筛法直接筛出范围内所有质数撑不住,于是我们把目光放到
易得:区间
因此我们处理
区间筛法可以写成:
if(l==1) v[0]=1;//1不是质数!
for(int i=1;i<=m&&prime[i]*prime[i]<=r;i++)
{//prime[i]存的是sqrt(R)的质数
long long p=prime[i];
long long start=max((l+p-1)/p*p,2*p);
//start是处在区间内能被p整除的第一个数
// (l+p-1)是用于向下取整,后面会解释
for(long long j=start;j<=r;j+=p)
{
v[j-l]=1;//j比较大,我们用v[j-l]表示j是否被标记
}
}
cnt=0;
for(long long i=l;i<=r;i++)//以防爆int
{
if(v[i-l]==0)
{
pri[++cnt]=i;//区间内质数
}
}
这里需要解释一下为什么是
-
首先我们知道,要标记合数,所以这个数必须是
p 的k(k>1) 倍,所以为了防止自己本身被标记,所以要**判断 -
我们假设
(l+p-1)/p \ge 2 ,现验证(l+p-1)/p*p 为大于L 的且能被整除的第一个数
规定:
从以上可以推出,不仅是
#include<iostream>
#include <cstdio>
#include <cstring>
using namespace std;
const int MAXN=1e6+10;
int prime[4800];
int v1[46341];
int m;//4792
int l,r,cnt;
int v[MAXN];
int pri[MAXN];
int x1,y1,x2,y2;
inline void getpri()
{
for(int i=2;i<=46340;i++)
{
if(v1[i]==0)
{
prime[++m]=i;
v1[i]=i;
}
for(int j=1;j<=m;j++)
{
int p=prime[j];
if(p>v1[i]||p*i>46340) break;
v1[p*i]=p;
}
}
}
int main()
{
//freopen("in.txt","r",stdin);
scanf("%d%d",&l,&r);
getpri();
memset(v,0,sizeof(v));
if(l==1) v[0]=1;//1不是质数!
for(int i=1;i<=m&&prime[i]*prime[i]<=r;i++)
{//prime[i]存的是sqrt(R)的质数
long long p=prime[i];
long long start=max((l+p-1)/p*p,2*p);//减2也能过
//start是处在区间内能被p整除的第一个数
// (l+p-1)是用于向下取整,后面会解释
for(long long j=start;j<=r;j+=p)
{
v[j-l]=1;//j比较大,我们用v[j-l]表示j是否被标记
}
}
cnt=0;
for(long long i=l;i<=r;i++)//以防爆int
{
if(v[i-l]==0)
{
cnt++;//区间内质数
}
}
printf("%d",cnt);
}
//要记得注释掉open