有关素数的一些算法和定理
素数的判定:
- 费马小定理——用于素数的快速判定
对于质数p,任意整数a均满足:ap ≡ a( mod p)
素数的筛选:
-
素数埃氏筛法
求区间【2,n】中的所有素数
1)输出最小的2,再筛掉序列中所有2的倍数,剩下{3,5,7,9,11……n}
2)输出最小的3,再筛掉序列中所有3的倍数,剩下{5,7,11……n}
3)输出最小的5,再筛掉序列中所有5的倍数,剩下{7,11……n}
不断重复上述步骤,直到剩下n -
素数线性筛法(欧拉筛法)
这是为了解决素数埃氏筛法的不足——对于一个合数来说,例如56,在埃氏筛法中会被多次筛——2、7,但这些重复是没有必要的欧拉筛法思想——
在艾氏筛法的基础上,让每个合数只被它的最小质因子筛选一次,以达不重复的目的。代码——
//欧拉筛法
const int N = 1000001;
int primes[N],cnt;
bool ct[N];
void eluer(int n)
{
//初始化数组
memset(ct,false,sizeof(ct));
cnt = 0;
for(int i=2; i<n; i++)
{
if(!ct[i])
{
primes[cnt++] = i;
//cout << i << endl;
}
for(int j=0; j<cnt&&i*primes[j]<n; i++)
{
ct[i*primes[j]] = true;
if(i%primes[j] == 0)
break;
}
}
}
if(i%primes[j] == 0)
break;
这一句是重点——
i%primes[j] == 0说明i是primes[j]的倍数,即 i= k*primes[j] 。如果不break的话,下一次应该就是置 ct[i*primes[j+1]] 为true。
i * primes[j+1] = k * primes[j] * primes[j+1] = primes[j] * k * primes[j+1]
而欧拉筛法所采用的思想就是只用合数的最小素因子去筛,显然不break的话,黄色部分就采用了另一个素因子重复在筛这个合数。
为检验学习成果,特地去找了一道题来刷……果然,我还是个弟弟
很经典的一道例题——Prime Distance
链接:POJ 2689
题目大意:输入两个数表示范围,求这个范围内的相邻素数中间隔最大和最小的两个素数。
解题过程:由于所给数据太大了,不能直接进行求解,可以采取先求出前50000中的所有素数,然后再映射到所给的区间。前一步很容易做到,直接调用模板函数就可以了,后面的映射就有一些麻烦。
进行区间映射:前50000内的素数我们是已知的,如今的关键就是去看所给的范围究竟是前50000得到的素数的几倍,然后把其中的合数筛出来,这里有两种做法:
1)采用求模的方法
2)采用直接相除的方法
左边界的倍数:a = (l-1) / su[i] +1
右边界的倍数: b = r / su[i]
代码——
定义MM上界的时候一定要多加注意,不然会wa
#include <stdio.h>
#include <string.h>
#include <iostream>
#include <algorithm>
using namespace std;
#define MAX 50005
#define MM 1000005 //啊啊啊这个地方坑死我了,千万不要漏掉0不然会报错超时
#define INF 1e9
int su[MAX],f[MM];
bool primes[MAX];
int cnt = 0;
//先筛50000内的素数——欧拉筛法
void get_primes()
{
//初始化数组
memset(primes,false,sizeof(primes));
for(int i=2; i<MAX; i++)
{
if(!primes[i])
su[cnt++] = i;
for(int j=0; j*su[i]<MAX && j<cnt; j++)
{
primes[j*su[i]] = true;
if(i%su[j] == 0)
break;
}
}
}
int main()
{
get_primes();
int l,r;
while(cin >> l >> r)
{
//第一步是映射
if(l==1)
l = 2;
//l如果是1的话,求倍数的时候就会把素数本身当作合数进行标记
memset(f,0,sizeof(f));
int a,b;
for(int i=0; i<cnt; i++)
{
a = (l-1)/su[i] +1;
b = r/su[i];
for(int j=a; j<=b; j++)
if(j>1)
f[su[i]*j -l] = 1;
}
//此时是合数的已经置为1
//下面可以采取暴力枚举的方法
int x1,x2,x3,x4,p=-1,max_ele=-1,min_ele=INF;
for(int i=0; i<=r-l; i++)
{
if(f[i]==0)
{
if(p==-1)
{
p = i;
continue;
}
if((i-p)>max_ele)
{
max_ele = f[i]-p;
x1 = p+l;
x2 = i+l;
}
if((i-p)<min_ele)
{
min_ele = f[i]-p;
x3 = p+l;
x4 = i+l;
}
p = i;
}
}
if(max_ele==-1)cout<<"There are no adjacent primes."<<endl;
else cout<<x3<<","<<x4<<" are closest, "<<x1<<","<<x2<<" are most distant."<<endl;
}
return 0;
}
写一道题不容易啊呜呜呜~
-
还有一个知识点是反素数
反素数定义:
对于任意的小于x的正整数 i,都有g(x)>g(i) ,则称x为反素数。
反素数可化为:? = ?1?1?2?2 … ????
性质:
1)质因子 ?1, ?2, …, ?? 是从2开始连续的素数
2)质因子的指数递减?1 ≥ ?2 ≥ ?3 … ≥ ?r这里给出一个大佬的博客链接,专门总结了反素数,太腻害了——博客地址
有关于反素数的例题——
题目链接:BZOJ1053 反素数
题意:给定一个N,求出不超过N的最大的反素数
解题过程:刚刚给出的链接大佬博客里面有讲这题,并进行了专门分析,我也拙略的分析一下——
题目范围≤2∗109,素数相乘得:2∗3∗5∗7∗11∗13∗17∗19∗23∗29∗31>2∗109,且根据性质知素数的指数是递减的,所以可以采取dfs方式。代码——
#include <iostream>
#include <string.h>
#include <stdio.h>
using namespace std;
typedef unsigned long long ULL;
const ULL INF = ~0ULL;//无符号长整型的0
int su[]={2,3,5,7,11,13,17,19,23,29,31};
ULL best,p,n;
void dfs(int dept, int plu, int num)
{
//dept表示数的层数,从0开始,即10
//plu表示当前的相乘得到的数
//num表示所含有的质数的个数
if(dept>=11) //到达边界值
return;
if(num > best)
{
//best记录原先所含的最多的质因子
best = num;
p = plu;
//更新反素数的值
}
if(num==best && plu<p)
p = plu;
//开始递归部分
for(int i=1; i<=45; i++)
{
if(n/su[dept] < plu)
break;
dfs(dept+1, plu*=su[dept], num*(i+1));
}
}
int main()
{
while(cin >> n)
{
p = INF;
best = 0;
dfs(0,1,1);
cout << p << endl;
}
return 0;
}