bool isp(int n)
{
if(n<2)//0,1 不是素数
return 0;
int m=sqrt(n+0.5);
for(int i=2;i<=m;i++)
if(n%i==0)
return 0;
return 1;
}
性能分析
1. 期望时间复杂度O(n^(1/2));
2. 只有在少量判断的时候使用
bool vis[maxn];
void Init(int n)
{
memset(vis,1,sizeof(vis));//因为是bool所以可以直接用
vis[0]=vis[1]=0;
for(int i=2;i<=n;i++)
{
if(vis[i])
for(int j=2*i;j<=n;j+=i)
{
vis[j]=0;
}
}
}
性能分析
1. 期望时间复杂度:O(nlogn)
2. 这个最厉害的地方是可以筛选很多东西,因为每个数都会被它的所有质因数筛一次,比后文的线性筛更加有用
3. 进一步优化,j从i*i开始,每个数只会被它较小的素数数筛出,期望时间复杂度O(nloglogn),几乎是线性,但是运用范围窄,不如一般筛选法
注:上面的所有log都是以自然对数为底数。
(它不完全是线性的,但是每个元素都只会被筛选一次。)
bool vis[maxn];
int isp[maxn],sz;
void Init(int n)
{
memset(vis,1,sizeof(vis));
vis[0]=vis[1]=0;//特判
for(int i=2;i<=n;i++)
{
if(vis[i])pri[++sz]=i;
for(int k=1;k<=sz && pri[i]*k<=n ;k++)//注意这里是是不是素数都要筛
{
vis[i*pri[k]]=0;
if(i%pri[k]==0)//##
break;
}
}
}
理解:
1. 如果i是素数,那么当前筛的数肯定不会出现之前筛出的数(因为之前压根没有这个数出现,更不说这个数的倍数)。
2. 如果i是合数,正如##处,i*pri[k]保证pri[k]不超过i的最小素数,但是并不会证明为什么筛出了所有素数,感性理解一下举几个例子吧,并不会证。
性能分析
1. 时间复杂度:O(n)
2. 每个数只会被它最小的素数因子筛去。
3 其他作用:可以求很多积性函数,比如欧拉函数
定理1:费马小定理:假如p是素数,且 (a,p)=1 ( a , p ) = 1 ,那么 ap−1≡1(modp) a p − 1 ≡ 1 ( m o d p ) 。
定理2:如果p是素数,x是小于p的正整数,且 x2≡1(modp) x 2 ≡ 1 ( m o d p )
这两个定理的逆定理不总是成立,但是大多数情况成立,因此多次测试即可有比较大的概率判断对。
过程是这样的:
1. 设判定的数为n,特判0,1,2和偶数的情况。
2. 分解指数 n−1=r∗2d n − 1 = r ∗ 2 d ,其中t尽量大。
3. 随机取一个正整数a作为底数。
4. 计算序列 ar,a2∗r,a2∗2∗r,a2∗2∗2∗r... a r , a 2 ∗ r , a 2 ∗ 2 ∗ r , a 2 ∗ 2 ∗ 2 ∗ r . . . 在mod n下的值。从第二项开始,如果某一项的值是1且它前面那一项的值不是mod n下的1或-1(即n-1),则返回false(定理2)
5. 最后一项如果不是1则返回false(定理1)
6. 返回3,进行10次左右的测试,若一直没有返回false,就返回true。
typedef long long LL;
LL mul(LL a,LL b,LL mod)//一个LL*LL的黑科技
{
LL tmp=(a*b-(LL)((long double)a/mod*b+1e-8)*mod);//换成double乱搞
return tmp<0?tmp+mod:(tmp>=mod?tmp-mod:tmp);//就是取mod
}
LL qkpow(LL a,LL p,LL mod)//快速幂
{
LL t=1,tt=a%mod;
while(p)
{
if(p&1)t=mul(t,tt,mod);//所有乘法都要用这个厉害的乘法哦
tt=mul(tt,tt,mod);
p>>=1;
}
return t;
}
bool Miller_Rabin(LL n)//可能是素数就返回true,任何一个测试不通过就不是素数,返回false
{
if(n==2)return true;
if(n<2 || ~n&1)return false;//先要特判排除两种情况
LL r=n-1,d=0,a,x,xx;//x是前一次的值,xx是当前值
while(~r&1)d++,r>>=1;
for(int i=1;i<=10;i++)
{
a=rand()%(n-2)+2;
x=qkpow(a,r,n);
for(int k=1;k<=d;k++)//直接默认从序列第二个元素开始,因此只循环d次
{
xx=mul(x,x,n);//所有乘法都要用这个厉害的乘法哦
if(xx==1 && x!=1 && x!=n-1)return false;//二次探测定理
x=xx;
}
if(xx!=1)return false;
}
return true;
}
int main()
{
LL n;
while(cin>>n)
Miller_Rabin(n)?printf("Y\n"):printf("N\n");
return 0;
}
性能分析:
1. 这个算法错误率好像是 14k 1 4 k ,其中k是不同底测试次数,如果判定结果为合数是不会错的。
2. 时间复杂度是O(Klogn)判断每个数。
之所以不用费马小定理的逆定理直接判定,是因为有一类叫做卡米切尔数的神奇数字使得对于任意a都能通过费马小定理的测试但是它是合数。
好消息是这样的数字并不多,而且最小因数不大,所以可以直接拿几百个素数去筛一遍,然后用费马小定理逆定理去判断一下即可。
就是把一个数分解质因数。
LL a;
int p[maxn],e[maxn],num;
int getZys(LL a,int *p,int *e)
{
int num=0;
int m=sqrt(a+0.5);//所有的取根号都要这么做
for(int i=2;i<=m;i++)//一定是2开始,虽然可以很容检查出来
{
if(a%i==0)
{
p[++num]=i;
e[num]=0;
while(a%i==0)
e[num]++,a/=i;
m=sqrt(a+0.5);//一个比较皮的常熟优化
}
}
if(a!=1)
{
p[++num]=a;
e[num]=1;
}
return num;
}
性能分析:
1. 时间复杂度O(sqrt(n))
2. 针对一般的数随随便便分解即可,如果有素数表的话用素数表也是极好的。
定理:对于小于 n 的质数 p,n!中含有因子 p 的个数为:n/p+n/p^2+…+n/p^k(其中 k为<=p^n的最大值)
bool vis[maxn];
int isp[maxn],nump;
int e[maxn];
void get_p(int n)//线性筛
{
memset(vis,1,sizeof(vis));
vis[0]=vis[1]=0;
for(int i=2;i<=n;i++)
{
if(vis[i])
isp[++nump]=i;
for(int k=1;k<=nump && isp[k]*i<=n;k++)
{
vis[isp[k]*i]=0;
if(i%isp[k]==0)
break;
}
}
}
void getJsZys(int n,int *e)
{
LL p;//注意开LL
for(int i=1;i<=nump;i++)
{
p=isp[i];
while(p<=n)
{
e[i]+=n/p;
p*=isp[i];
}
}
}
注意:
1. 这里不要用什么等比数列,因为这里要用到舍尾,加了一进位就错,况且硬算其实不慢
2. 这里的质因数要一直到n,因此先要筛选素数
性能分析:
1. 时间复杂度,大概是O( sqrt(n)logn s q r t ( n ) l o g n )的样子吧,会快一些
2. 用于求组合数,但是很多时候我们更喜欢逆元。(在有逆元的情况下)
#include
using namespace std;
typedef long long LL;
LL p[100];
int sz;
LL mul(LL a,LL b,LL mod){//一个LL*LL的黑科技
LL tmp=(a*b-(LL)((long double)a/mod*b+1e-8)*mod);//换成double乱搞
return tmp<0?tmp+mod:(tmp>=mod?tmp-mod:tmp);//就是取mod
}
LL qkpow(LL a,LL p,LL mod)
{
LL t=1,tt=a%mod;
while(p)
{
if(p&1)t=mul(t,tt,mod);//所有乘法都要用这个厉害的乘法哦
tt=mul(tt,tt,mod);
p>>=1;
}
return t;
}
bool Miller_Rabin(LL n)//可能是素数就返回true,任何一个测试不通过就不是素数,返回false
{
if(n==2)return true;
if(n<2 || ~n&1)return false;//先要特判排除两种情况
LL r=n-1,d=0,a,x,xx;//x是前一次的值,xx是当前值
while(~r&1)d++,r>>=1;
for(int i=1;i<=10;i++)
{
a=rand()%(n-2)+2;
x=qkpow(a,r,n);
for(int k=1;k<=d;k++)//直接默认从序列第二个元素开始,因此只循环d次
{
xx=mul(x,x,n);//所有乘法都要用这个厉害的乘法哦
if(xx==1 && x!=1 && x!=n-1)return false;//二次探测定理
x=xx;
}
if(xx!=1)return false;
}
return true;
}
LL gcd(LL a,LL b)
{
return !b?a:gcd(b,a%b);
}
LL rho(LL n,LL c)
{
LL i=1,k=2,x=rand()%n,y=x,t=n;
while(1)
{
i++;
x=(mul(x,x,n)+c)%n;//生成数
t=gcd(abs(x-y),n);
if(t>1 || y==x)break;
if(i==k)y=x,k<<=1;//奇特判圈法
}
return t;
}
void zys(LL n)
{
if(n==1)return;
if(Miller_Rabin(n)){p[++sz]=n;return;};
LL t=n;
while(t>=n)t=rho(n,rand()%(n-1)+1);
zys(t);
zys(n/t);
}
int main()
{
//freopen("in.txt","r",stdin);
//freopen("out.txt","w",stdout);
LL n;
cin>>n;
zys(n);
sort(p+1,p+sz+1);
return 0;
}
-时间复杂度:在O(n^(1/4))的时间内找出一个因数,LL范围内因数最多的数可能有几千个吧。
- 注意要给分解出来的质因数的排序