板子:素数的判定以及整数的唯一分解定理

素数判定

方法一:定义判定法

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 其他作用:可以求很多积性函数,比如欧拉函数

方法四:米勒拉宾(Miller_Rabin)素数测试法

定理1:费马小定理:假如p是素数,且 (a,p)=1 ( a , p ) = 1 ,那么 ap11(modp) a p − 1 ≡ 1 ( m o d p )
定理2:如果p是素数,x是小于p的正整数,且 x21(modp) x 2 ≡ 1 ( m o d p )
这两个定理的逆定理不总是成立,但是大多数情况成立,因此多次测试即可有比较大的概率判断对。

过程是这样的:
1. 设判定的数为n,特判0,1,2和偶数的情况。
2. 分解指数 n1=r2d n − 1 = r ∗ 2 d ,其中t尽量大。
3. 随机取一个正整数a作为底数。
4. 计算序列 ara2r,a22r,a222r... 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!的方法

定理:对于小于 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. 用于求组合数,但是很多时候我们更喜欢逆元。(在有逆元的情况下)

pollard_rho算法

  • 根据玄学的概率模型,快速求出一个数的一个因数,然后递归处理,结合米勒拉宾算法判断是否还需要分解。
  • 这个概率模型大概就是生日悖论,指生成一些数,任意取两个数的gcd是原数的因数的概率很大。
  • 还有用一个类似Floyed判圈法的一个方法在生成的数中出现环的时候跳出。
#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范围内因数最多的数可能有几千个吧。
- 注意要给分解出来的质因数的排序

说几个关于因数的应用

  • 可以求n^m的质因数
  • 可以求因数个数: (e1+1)(e2+1)...(en+1) ( e 1 + 1 ) ∗ ( e 2 + 1 ) ∗ . . . ∗ ( e n + 1 ) ,其中e是质因数的个数。
  • 可以求因数和: (1+p11+...+pe11)(1+p12+...+pe22)..(1+p1n+...+penn) ( 1 + p 1 1 + . . . + p 1 e 1 ) ∗ ( 1 + p 2 1 + . . . + p 2 e 2 ) ∗ . . ∗ ( 1 + p n 1 + . . . + p n e n ) ,具体的话可以用等比数列求和公式+逆元或者用分治+快速幂来做。
  • 2e9内因数个数最多的是1396755360,有1536个因数,可能LL范围内因数不会太多?

你可能感兴趣的:(学习笔记/板子,模板,数学,数学板子)