素数表(线性筛+数位优化)

4 篇文章 0 订阅
1 篇文章 0 订阅

一、判断素数

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 n5
可以分成以下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}} πxlnxx,可以用来近似估算不超过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,再往后因为正常的欧拉筛爆了,所以没有验证了,欢迎指正。
话说我又给自己挖了好大一个坑啊,前面那么多空哪天补回来。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值