数论 质数

质数

筛法求质数

朴素筛法

原理

\[\begin{align} &\forall x\in[2,\text{maxn}]\notag\\ &kx,k\in[2,\text{maxn}]\ 都不可能为质数\notag \end{align} \]

用法

bool mark[maxn];//mark[i]==true means i is no a prime num
inline void prim(int maxn){
    for(int i=2;(i<<1)<=maxn;i++)//note
        for(int j=i+i;j<=maxn;i+=i)mark[j]=true;
}
//note: when i*2>maxn, evidently there's not any
//unmarked number between i and maxn
//so we just need to for() until i*2>maxn

埃氏筛法

原理

\[\begin{align} \because\ &\forall x\in[2,\text{maxn}],且x为质数\notag\\\notag \therefore\ &kx,k\in[2,\text{maxn}]\ 都不可能为质数\\\notag \\\notag 在循环中,\ &若mark[i]=\text{false}\\\notag 有\ &i是质数\\\notag 证明:\ \ \\\notag \because\ &mark[i]=\text{false}\\\notag \therefore\ &\forall k\in [2,i-1],k\nmid i\\\notag \therefore\ &根据质数的定义,i是一个质数\\\notag 得证\ \ \ \ \ \ \end{align} \]

用法

bool mark[maxn];
inline void prim(int maxn){
    for(int i=2;(i<<1)<=maxn;i++)
    	if(!mark[i])
            for(int j=i+i;j<=n;j+=i)mark[j]=true;
}

线性选法

原理

\[\begin{align} 记\ &p_i为第i个质数\notag\\\notag \therefore\ &对于数i\\\notag 若\ &i\%p_j=0\\\notag 有\ &p_j是i的最小质因子,也是i\times p_j的最小质因子\\\notag 若\ &i\%p_j\not=0\\\notag 有\ &p_j\lt i的最小质因子,且p_j是i\times p_j的最小质因子\\\notag \end{align} \]

正确性证明

\[\begin{align}\notag 求证:\ 若&p_j是x 的最小质因数,当i枚举到x/p_j时,x会被筛掉\\\notag 证明: 设\ &p_j是x的最小质因子\\\notag 则\ &当i=\frac x{ p_j}时,\\\notag \because\ &p_j\le\frac x {p_j} \\\notag \therefore\ &p_j一定被枚举过了\\\notag \therefore\ &x会被p_j筛掉 \end{align} \]

用法

const int maxn=1e4;
bool mark[maxn];
int primes[maxn];
int cnt;
inline void prime(int n){
    for(int i=2;(i<<1)<=n;i++){
        if(!mark[i]) primes[++cnt]=i;
        for(int j=1;primes[j]<=n/i;j++){
            mark[primes[j]*i]=true;
            if(i%primes[j]==0)break;
        }
    }
}

e.g

求能被 \([1,n]\) 中每个数所整除的数

思路分析

我们先来模拟一下 \([1,6]\) 的答案:

\[\begin{align} \notag 当数列为\ &\{1,2\}时\\\notag ans&=2\\\notag 当数列为\ &\{1,2,3\}时\\\notag ans&=6\\\notag 当数列为\ &\{1,2,3,4\}时\\\notag ans&=12\\\notag 当数列为\ &\{1,2,3,4,5\}时\\\notag ans&=60\\\notag 当数列为\ &\{1,2,3,4,5,6\}时\\\notag ans&=60 \end{align} \]

发现什么规律了吗?

只有当数列中加入形如 \(p^i,p为质数\) 的数时,需要将 \(p\) 乘入答案

为什么呢?

\[\begin{align} \notag证明:&\ \\\notag i)\ &当加入数列的数i为质数时\\\notag &\because\ ans已经能被[1,i-1]中的每个数整除\\\notag &换言之,\forall x\in[1,i-1],有\gcd(x,ans)\not=1\\\notag &\therefore\ ans=x_1\times x_2\times x_3\dots\times x_j,\\\notag\ & A=\{x_1,x_2,x_3\dots,x_j\}\subseteq[1,i-1]\\\notag &\because\ \forall x_j\in A,\gcd(x_j,i)=1\\\notag &且\ \gcd(a,p)=1,\gcd(b,p)=1\Rightarrow\gcd(a\times b,p)=1\\\notag &\therefore\ \gcd(ans,i)=1\\\notag &\therefore\ i应当乘入ans\\\notag ii)\ &当加入数列的数i能表示为p^k,且p为质数时\\\notag 用数学&归纳法证明:\\\notag I)\ &对于每一个质数p,当t=1时,由i),有p应当乘入ans\\\notag II)\ &对于每一个质数p,当t>1,t\in\N^+时\\\notag &若当k=t时题设成立,则当k=t+1时\\\notag &ans=p^t\times C,\gcd(C,p)=1\\\notag &\because\ i=p^{t+1}\\\notag &\therefore\ \frac {ans} i=\frac C p\\\notag &\because\ \gcd(C,p)=1\\\notag &\therefore\ 应当将p乘入ans\\\notag &\therefore\ 题设成立\\\notag \therefore\ &综上I)II)\\\notag &当加入数列的数i能表示为p^k,且p为质数时,应当将p乘入ans\\\notag iii)\ &当加入数列的数i既不是质数,也不是质数的幂次方时\\\notag &\therefore i=x\times p^k,p^k为i最大的因子,p为质数\\\notag &\therefore x\lt p^k\lt i\\\notag &\therefore 根据ii)\ ,\gcd(ans,p^k)\not=1\\\notag &若\ x是质数,则根据i)\ ,\gcd(ans,x)\not=1\\\notag &若\ x可以表示为一个质数的幂次方,则根据ii)\ ,\gcd(ans,x)\not=1\\\notag &若\ x不属于上述的数,则x 可以像i一样继续分解,直至\\\notag &x_k是质数或质数的幂次方\\\notag &\therefore \gcd(i,ans)\not=1\\\notag &\therefore i不用乘入ans\\\notag 得证 \end{align} \]

上述证明的第一步是证明所有质数需要乘入答案,第二步是证明所有可以表示为质数的幂次方的数的对应质数需要乘入答案,第三步是证明其他数不乘入答案

根据这个证明,我们可以写出一个简单的代码

#include<bits/stdc++.h>
#define GO(u,v,i) for(register int i=u;i<=v;i++)
using namespace std;
//fast-read
template<class t>inline t fr(){
    register t num=0,dis=1;
    register char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')dis=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){num=(num<<1)+(num<<3)+(ch^48);ch=getchar();}
    return dis*num;
}
//fast-write
template<class t>inline void fw(t num){
    if(num>9)fw(num/10);
    putchar(num%10+'0');
}
template<class t>inline void fw(t num,char ch){
    if(num<0){
        num=-num;
        putchar('-');
    }
    fw(num);putchar(ch);
}
const int maxn=5e7+12;
bool mark[maxn];//被标记的数不是质数
int primes[maxn];//质数序列
int min_pri[maxn];//每个数的最小质因数
int cnt;//质数数量
//线性筛法求质数
inline void sol(int n){
    for(int i=2;i<=n;i++){
        //如果一个数是质数,那么它的最小质因子就是它本身
        if(!mark[i])primes[++cnt]=i,min_pri[i]=i;
        for(register int j=1;i*primes[j]<=n;j++){
            mark[i*primes[j]]=true;
            //求出i*primes[j]的最小质因子
            min_pri[i*primes[j]]=primes[j];
            if(i%primes[j]==0){
                min_pri[i]=primes[j];
                break;
            }
        }
    }
}
inline void check(int n){
    //解释
    //如果一个数不断除它的最小质因子,除到无法整除为止
    //此时若商为1,则说明这个数是对应最小质因子的幂,要乘入答案
    //否则不能乘入答案
    GO(2,n,i){
        int temp=i;
        while(min_pri[(temp=temp/min_pri[i])]==min_pri[i]);
        if(temp!=1)min_pri[i]=1;
    }
}
typedef long long lld;
signed main(){
    int n=fr<int>();
    sol(n);
    check(n);
    lld ans=1;
    GO(2,n,i){
        ans*=min_pri[i];ans%=(int)1e8+7;
    }
    fw(ans,'\n');
    return 0;
}

但是这样写只有 \(60\) 分,因为 \(O(n+nlogn)\) 实在是太大了

我们可以怎样优化呢?

我们发现,所有要乘入 \(ans\) 的数其实就是每一个质数的所有次幂

所以可以在筛的过程中就把 \(ans\) 求出来

AC代码

#include<bits/stdc++.h>
#define GO(u,v,i) for(register int i=u;i<=v;i++)
using namespace std;
template<class t>inline t fr(){
    register t num=0,dis=1;
    register char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')dis=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){num=(num<<1)+(num<<3)+(ch^48);ch=getchar();}
    return dis*num;
}
template<class t>inline void fw(t num){
    if(num>9)fw(num/10);
    putchar(num%10+'0');
}
template<class t>inline void fw(t num,char ch){
    if(num<0){
        num=-num;
        putchar('-');
    }
    fw(num);putchar(ch);
}
const int maxn=5e7+12;
typedef long long lld;
bool mark[maxn];
lld pri[maxn],tot;
lld minpri[maxn];
lld ans;
inline void sol(lld n){
    for(lld i=2;i<=n;i++){
        if(!mark[i]){
            //i是质数,把所有i^j都加入
            for(lld j=i;j<=n;j*=i){
                ans=(ans*i)%((lld)1e8+7);
            }
            pri[++tot]=i;
        }
        for(register lld j=1;i*pri[j]<=n;j++){
            mark[i*pri[j]]=true;
            if(i%pri[j]==0)break;
        }
    }
}
signed main(){
    ans=1;
    lld n=fr<lld>();
    sol(n);
    fw(ans,'\n');
    return 0;
}
posted @ 2022-05-25 21:22  Locked_Fog  阅读(158)  评论(0)    收藏  举报