素数定义:素数就是大于1的正整数,并且只能被自己和1整除的数,与之相对应的就是合数;
素数与合数性质:
(1)a>1是合数,当且仅当a=bc,其中1<b<a,1<c<a
(2)合数必有素数因子
(3)如果d>1,,p是素数,并且d|p,则p=d
(4)p是素数且p|ab,那么必有p|a或者p|b
(5)每个大于1的正整数都有一个素因子
素数定理:
随着x的增长,f(x)/(x/lnx)=1;其中f(x)表示小于x的素数个数
推论:令Pn是第n个素数,其中n为正整数,那么Pn~nln n
素数定理常用来大概的判断素数的分布问题
素数判定:
(1)直接暴力
思想:从2开始枚举,直到sqrt(n),只要里面又一个数能被n整除,那么就说明这个数不是素数,反之,如果所有的数都不能被整除,那就说明n是一个素数
int is_prime(int n)
{
for(int i=2;i<=sqrt(n);i++)
{
if(n%i==0)
return 0;
}
return 1;
}
(2)筛选法
思想:要求10000以内的所有素数,把1-10000这些数都列出来,1不是素数,划掉;2是素数,所有2的倍数都不是素数,划掉;取出下一个幸存的数,划掉它的所有倍数;直到所有幸存的数的倍数都被坏掉为止。 要找出10000以为的所有的素数,则需要一个大小为10000的数组,将其所有元素设置为未标记 首先把1设置为标记,从2开始,标记所有是它倍数的数,然后对下一个没有标记的数进行标记它的倍数。 当标记完成后,所有未标记的数即为素数。
代码:
bool isprime[maxn];//标记
int prime[maxn];//记录素数
int numprime;//计录素数的个数
void doprime()//埃拉托斯尼斯素数筛选法
{
numprime=0;
memset(isprime,true,sizeof(isprime));
isprime[1]=false;
for(int i=2; i<maxn; i++)
{
if(isprime[i])
{
prime[++numprime]=i;
for(int j=i*i; j<maxn; j+=i)
isprime[j]=false;
}
}
}
当然也可以不打表通过筛选法来判断一个数是不是素数,思想和上面是一样的
bool isprime(int n)
{
if(n==1)return false;
if(n==2)return true;
if((n&1)==0)return false;
for(int i=3; i*i<=n; i+=2)
if(n%i==0)
{
return false;
break;
}
return true;
}
素数判定的应用:http://poj.org/problem?id=2689
代码:http://hi.baidu.com/tianzhang0000/item/b985ebf08e3b0843c9f33792
Miller-rabin测试
/******************************************
费马小定理:假如p是素数,并且(a,p)=1那么a^(p-1)=1(mod p);
威尔逊定理:如果p是素数,(p-1)!=-1(mod p)
费马小定理推论:假如p是素数,且a是正整数,那么a^p=a(mod p);
设n是一个正整数,欧拉函数f(n)定义为不超过n且与n互素的正整数的个数
欧拉定理:设m是一正整数,a是一个整数且(a,m)=1,那么a^f(m)=1(mod m)
miller rabin测试原理:费马小定理
伪素数:a是一正整数,如n是合数且满足a^n=a(mod n),那么n称为以a为基的伪素数
如果存在n不满足a^n=a(mod n),那么n就一定不是素数,当然如果n满足了也不一定是素数
卡迈克尔数:一个合数n,对所以满足(b,n)=1的正整数都有b^(n-1)=1(mod n)。
因此,我们在利用费马小定理来判断素数时,要排除掉卡迈克尔数
二次探测定理:如果p是一个素数,且0<x<p,则方程x^2%p=1的解为x=1或者x=p-1
那么可以根据二次探测定理,在利用费马小定理计算b^n-1%n的过程中增加对整数n的二次探测,一旦发现违背二次探测定理的,就不是素数
******************************************/
#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cmath>
#include<ctime>
#include<cstdlib>
#define N 5 //测试试验次数
using namespace std;
long long random(long long n)//产生一个随机数
{
return (long long)((double)rand()/RAND_MAX*n+0.5);
}
long long multi(long long a,long long b,long long n)//计算a*b%n---二分的思想,用加法来模拟乘法,避免溢出
{
long long ans=0;
while(b)
{
if(b&1)
{
ans=(ans+a)%n;
}
b>>=1;
a=(a+a)%n;
}
return ans;
}
long long quick_mod(long long a,long long b,long long m)//计算a^b%n
{
long long ans=1;
while(b)
{
if(b&1)
{
ans=multi(ans,a,m);
b--;
}
b>>=1;
a=multi(a,a,m);
}
return ans;
}
bool witness(long long a,long long n)//二次探测定理的实现
{
long long m=n-1;
int j=0;
while(!(m&1))
{
j++;
m=m/2;
}
long long x=quick_mod(a,m,n);
if(x==1||x==n-1)
return false;
while(j--)
{
x=x*x%n;
if(x==n-1)
return false;
}
return true;
}
bool miller_rabin(long long n)//miller rabin算法
{
if(n<2)return false;
if(n==2)return true;
if(n%2==0)return false;
for(int i=1; i<=N; i++)
{
long long a=random(n-2)+1;
if(witness(a,n))return false;
}
return true;
}
int main()
{
int t,n;
srand(time(NULL));
cin>>t;
while(t--)
{
cin>>n;
if(n==1)break;
if(miller_rabin(n))cout<<"YES"<<endl;
else cout<<"NO"<<endl;
}
return 520;
}
应用:http://poj.org/problem?id=1811
代码:http://hi.baidu.com/tianzhang0000/item/ee7b00985c7147a583d295b0
另一个应用就是判定梅森素数
欧拉函数
设n是一个正整数,欧拉函数f(n)定义为不超过n且与n互素的正整数的个数
欧拉函数性质:它在整数n上的值等于对n进行素因子分解后,所有的素数幂上的欧拉函数之积
几个定理:
1、如果p是素数,那么f(p)=p-1;反之,如果p是一个正整数且满足f(p)=p-1,那么p一定是素数
2、设p是一个素数,n是一个正整数,那么f(p^n)=p^n-p^(n-1);
3、设m和n是互素的正整数,那么f(m*n)=f(n)*f(m);
4、设n=(p1^a1)*(p2^a2)……(pk^ak)为正整数n的素数幂分解,那么就有f(n)=n*(1-1/p1)*(1-1/p2)……(1-1/pk)
5、当n为奇数时,有f(n)=f(n*2);
6、设n是一个大于2正整数,那么f(n)为偶数
欧拉定理:设m是一正整数,a是一个整数且(a,m)=1,那么a^f(m)=1(mod m)
求欧拉函数的思想:定理4
直接实现:
int euler(int n)
{
int ans=n;
for(int i=2;i<=sqrt(n*1.0);i++)
{
if(n%i==0)
{
ans=ans-ans/i;
do
n=n/i;
while(n%i==0)
}
}
if(n>1)//当n素数时,定理1
ans=ans-ans/n;
return ans;
}
素数表实现
int euler(int n)//素数打表在这儿就不重复
{
int ans=n;
for(int i=0;prime[i]<=sqrt(n*1.0);i++)
{
if(n%prime[i]==0)
{
ans=ans-ans/prime[i];
do
n=n/prime[i];
while(n%prime[i]==0)
}
}
if(n>1)//当n素数时,定理1
ans=ans-ans/n;
return ans;
}
递推求欧拉函数
可以先设所有数的欧拉函数值为自己本身,有定理1可知,如果p是一个正整数且满足f(p)=p-1,那么这个数p就是素数,
在遍历过程中如果玉带欧拉函数与自身相等的情况,那么说明该数为素数,把这个数的欧拉函数值改变,同时也把能被这个数整除的数改变
void euler()
{
for(int i=1;i<=maxn;i++)e[i]=i;
for(int i=2;i<=maxn;i+=2)e[i]/=2;
for(int i=3;i<=maxn;i+=2)
{
if(e[i]==i)
{
for(int j=i;j<maxn;j+=i)
e[j]=e[j]/i*(i-1);
}
}
}
应用:hdu1659