一、判断素数
1、时间复杂度O( x 2 x^2 x2) (可能)
(1)原理
定义
(2)代码
for (int i=2;i<=n;i++)
{
bool flag=true;
for (int j=2;j<i;j++)
{
if (i%j==0)
{
flag=false;
break;
}
}
if (flag)
{
cout<<i<<" ";
}
}
2、上一种方法优化到 n ( n ) n\sqrt(n) n(n)
(1)原理
一个数的因数会有与之对应的另一个因数,两数相乘必不会大于这个数本身
(2)代码
for (int i=2;i<=n;i++)
{
bool flag=true;
for (int j=2;j*j<=i;j++)
{
if (i%j==0)
{
flag=false;
break;
}
}
if (flag)
{
cout<<i<<" ";
}
}
3、上一种方法优化到 n n 3 n\sqrt{\frac{n}{3}} n3n
(1)原理
这个是某天翻csdn的时候翻到的,看到的时候完全震惊了,欢迎知道出处的人联系我加上出处。
对于正整数
n
≥
5
n\geq 5
n≥5,
可以分成以下6类:
n
=
6
k
n=6k
n=6k,
n
=
6
k
+
1
n=6k + 1
n=6k+1,
n
=
6
k
+
2
n=6k + 2
n=6k+2,
n
=
6
k
+
3
n=6k + 3
n=6k+3,
n
=
6
k
+
4
n=6k + 4
n=6k+4,
n
=
6
k
+
5
n=6k + 5
n=6k+5。
当
n
=
6
k
n=6k
n=6k 明显是合数
当
n
=
6
k
+
2
n=6k + 2
n=6k+2,
n
=
6
k
+
3
n=6k + 3
n=6k+3,
n
=
6
k
+
4
n=6k + 4
n=6k+4,
有如下分解:
n
=
6
k
+
2
=
2
(
3
k
+
1
)
n=6 k+2=2 (3 k+1)
n=6k+2=2(3k+1),
n
=
6
k
+
3
=
3
(
2
k
+
1
)
n=6k+3=3(2k+1)
n=6k+3=3(2k+1),
n
=
6
k
+
4
=
2
(
3
k
+
2
)
n=6k+4=2(3k+2)
n=6k+4=2(3k+2)。故这三类数也是合数。
因此,只剩下
6
k
+
1
6k+1
6k+1 和
6
k
+
5
6k+5
6k+5有可能是素数。其实也就是:
当且仅当
n
=
6
k
±
1
n=6k\pm 1
n=6k±1 时,n才有可能是素数。这时,时间复杂度是
n
n
3
n\sqrt{\frac{n}{3}}
n3n
(2)代码
代码没有,因为我没敲过/找不到/懒
4、Miller_Rabin 算法
(1)原理
神仙算法,暂时解释不好,引用网上的解释
• 什么是Miller_Rabin算法:这个算法是由费马小定理推演而来。如果n是一个奇素数,将n-1表示成2s*r的形式,r是奇数,a与n是互素的任何随机整数,那么ar ≡ 1 mod n或者对某个j (0 ≤ j≤ s-1, j∈Z) 等式a^(2jr) ≡ -1 mod n 成立。(然而我也看不懂qwq)所以我们判断a^r ≡ 1 mod n或者对某个j (0 ≤ j≤ s-1, j∈Z) 等式a^(2jr) ≡ -1 mod n是否成立。如果任意一次不满足,那么就不是素数。
• 那么我们如何找a这个底数呢?下面是某dalao的话(我也忘记是谁说的了orz)
对于大数的素性判断,目前Miller-Rabin算法应用最广泛。一般底数仍然是随机选取,但当待测数不太大时,选择测试底数就有一些技巧了。比如,如果被测数小于4 759 123 141,那么只需要测试三个底数2, 7和61就足够了。当然,你测试的越多,正确的范围肯定也越大。如果你每次都用前7个素数(2, 3, 5, 7, 11, 13和17)进行测试,所有不超过341 550 071 728 320的数都是正确的。如果选用2, 3, 7, 61和24251作为底数,那么10^16内唯一的强伪素数为46 856 248 255 981。这样的一些结论使得Miller-Rabin算法在OI中非常实用。通常认为,Miller-Rabin素性测试的正确率可以令人接受,随机选取k个底数进行测试算法的失误率大概为4^(-k)。
所以我们在10000000这以内的数只需要用2,7,61这三个素数基本就能判断是否为素数了。
详细内容请自行搜索
(2)代码
#include<iostream>
#include<cstdio>
using namespace std;
int random[3]={2,7,61};//底数
long long Q_Pow(long long a,long long b,long long mod)
{
long long ret=1;
long long sum=a%mod;
while(b)
{
if(b&1)
{
ret=(ret*sum)%mod;
}
b>>=1;
sum=sum*sum%mod;
}
return ret;
}//快速幂,用来加快计算和防止爆long long
bool witness(long long a,long long n)//Miller Rabin算法的重点
{
long long tem=n-1;
int j=0;
while(tem%2==0)
{
tem/=2;
j++;
}//把n-1拆分为2^s*r的形式,这里j为s,tem为r
long long x=Q_Pow(a,tem,n);
if(x==1||x==n-1) return true;
while(j--)
{
x=x*x%n;
if(x==n-1) return true;
}//否则判断等式a^(2jr) ≡-1 mod n 看是否有满足的 j
return false;
}
bool Miller_Rabin(long long n)
{
if(n==2||n==7||n==61) return true;
if(n<2||n%2==0||n%7==0||n%61==0) return false;//因为要满足a与n是互素的任何随机整数,所以要特判
for(int i=0;i<3;i++)
{
long long a=random[i];
if(a!=n)
{
if(!witness(a,n)) return false;
}
}
return true;
}
int main()
{
int n,m;
scanf("%d%d",&n,&m);
long long tar;
for(int i=1;i<=m;i++)
{
scanf("%lld",&tar);
if(Miller_Rabin(tar)) cout<<"Yes\n";
else cout<<"No\n";
}
return 0;
}
题目链接:P3383 【模板】线性筛素数,话说这题最近更新数据给删题解了,这个算法好像没有了。
二、筛素数表
1、埃氏筛
(1)原理
buhui
(2)代码
没用过
2、欧拉筛
(1)原理
忘了
(2)代码
/*
bool Is_Prime[n+5];
int prime[n+5];
*/
void Make_Prime(int n)
{
memset(Is_Prime,true,sizeof(Is_Prime));
memset(prime,0,sizeof(prime));
Is_Prime[0]=Is_Prime[1]=false;
int top=0;
for (int i=2; i<=n; i++)
{
if (Is_Prime[i])
{
prime[top++]=i;
}
for (int j=0; j<top; j++)
{
if (prime[j]*i>n) break;
Is_Prime[prime[j]*i]=false;
if (i%prime[j]==0) break;
}
}
}
3、欧拉筛+数位优化
(1)原理
欧拉筛可以达到线性的复杂度,但是数组的大小限制了素数表的范围(自己测试100000好像就爆了)。但是 Is_Prime[] 和 Prime[] 两个数组都可以按如下方法优化。
1)对于 Is_Prime[] 数组:
c++中 int 整型占4个字节,32位。用二进制表示int类型,每一位只有两种可能0或1.
然后我们惊奇的发现 ,原本bool类型的数组每一位也只有两种情况 true 或 false(实际上也是0,1。不信看看隔壁python )。
那么我们就可以用一个int型表示32个数是否是素数(我采用了30),从左往右以此为1,2,3··32,内存损耗消耗缩减到
1
32
\frac{1}{32}
321 。同时,运用位运算可以快速确定到 int 的某一个二进制位。
2)对于Prime[]数组:
如何估计
n
n
n以内素数的个数以便节约空间呢?
通过 对素数表的观察 上网查找资料,在素数密度函数的证明中有一个近似公式,下面给出复制的内容:
在如此不规则的素数分布中发现了一个近似公式:用π(x)表示不超过x的素数个数,当x足够大时,
π(x)≈x/(lnx-1.08366)
这个公式的新近改进如下:
x/(lnx-0.5)√e3≈4.48169…成立.
比勒让德稍晚,1849年,德国大数学家高斯在给数学家恩克的信中也谈到,他以前考察过每千个自然数中的素数个数(据说,他研究了直到300万以内的一切素数的情形),因而发现了对于足够大的x的"素数平均分布稠密程度"π(x)/x≈1/lnx,也就是
π(x)≈x/lnx
这个结论后世称为素数定理,是数论乃至整个数学中最著名的定理之一.当初作为最著名的猜想,将素数个数同微积分中与生物增长有关的函数连接在一起,是离散量与连续量携手而震惊了整个数学界.
这个猜想的证明最初毫无进展,直到1852年左右,俄国著名的数学家切比雪夫首开纪录,证明了存在两个正常数a与b,使得如下不等式成立:
ax/lnx
简单地说,素数平均分布稠密程度
π
x
≈
x
lnx
\pi x\approx \frac{x}{\text{lnx}}
πx≈lnxx,可以用来近似估算不超过x的素数个数。
f
(
x
)
=
x
l
n
x
f(x)=\frac{x}{lnx}
f(x)=lnxx
所以,
P
r
i
m
e
[
]
Prime[]
Prime[] 数组可以用这个函数进行优化(下面给出的代码里面没有用到)
(2)代码
#include <iostream>
#include <cstring>
#define jud(x) Is_Prime[x/30]&(1<<(x%30))
#define era(x) Is_Prime[x/30]^=(1<<(x%30))
#define init 0x3FFFFFFF
#define MAXN 1000000
using namespace std;
int Prime[MAXN];
void Make_Prime(int n)
{
int Is_Prime[n/30+1];
memset(Is_Prime,init,sizeof(Is_Prime));
memset(Prime,0,sizeof(Prime));
int top=0;
for (int i=2; i<=n; i++)
{
if (jud(i))
{
Prime[top++]=i;
}
for (int j=0; j<top; j++)
{
if (Prime[j]*i>n) break;
era(Prime[j]*i);
if (i%Prime[j]==0) break;
}
}
}
int main()
{
Make_Prime(MAXN);
return 0;
}
4、theMeissel,Lehmer,Lagarias,Miller,Odlyzkomethod算法
(1)原理
直接搬运别人的解释,我看不懂
(2)代码
看看就好了
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
#include<cstdlib>
#include<ctime>
#pragma warning(disable:4996)
#define INF 2000000005
#define lowbit(a) ((a)&-(a))
#define FAIL -INF
const long long MAXN=6893911;
long long p[MAXN], cnt;
bool mark[MAXN];
int pi[MAXN];
void init()
{
long long i,j;
for (i=2;i<MAXN;i++)
{
if (!mark[i])
p[cnt++]=i;
pi[i]=pi[i-1]+!mark[i];
for (j=0;p[j]*i<MAXN&&j<cnt;j++)
{
mark[p[j]*i]=true;
if (i%p[j]==0)
break;
}
}
}
int f(long long n,int m)
{
if (n==0)return 0;
if (m==0)return n-n/2;
return f(n,m-1)-f(n/p[m],m-1);
}
int Pi(long long N);
int p2(long long n, int m)
{
int ans=0;
for (int i=m+1;(long long)p[i]*p[i]<=n;i++)
ans+=Pi(n/p[i])-Pi(p[i])+1;
return ans;
}
int p3(long long n, int m)
{
int ans=0;
for (int i=m+1;(long long)p[i]*p[i]*p[i]<=n;i++)
ans+=p2(n/p[i],i-1);
return ans;
}
int Pi(long long N)
{
if (N<MAXN)return pi[N];
int lim=f(N,0.25)+1;
int i;
for (i=0;p[i]<=lim;i++);
int ans=i+f(N,i-1)-1-p2(N,i-1)-p3(N,i-1);
return ans;
}
int main()
{
long long L,R;
scanf("%lld %lld",&L,&R);
init();
printf("%d",Pi(R)-Pi(L-1));
return 0;
}
tips
自己实现了这个方法,只验证到了10000,再往后因为正常的欧拉筛爆了,所以没有验证了,欢迎指正。
话说我又给自己挖了好大一个坑啊,前面那么多空哪天补回来。