【数论】| 判定质数、分解质因数、埃氏筛、线性筛

1. 试除法判定质数

bool isPrime(int x)
{
    if (x<2) return false;
    for (int i=2;i<=x/i;i++) 
        if (x%i==0)  return false;
    return true;    
}

2. 分解质因数

void divide(int x)
{
    for (int i=2;i<=x/i;i++) { //找当前的x的大于等于i的质数
        if (x%i==0) {//i从2开始,满足条件x%i为0的i一定都是质数,合数肯定不符合条件
            int s=0;
            while (x%i==0) s++,x/=i; //去除掉x的质因子i,所以x不再包含2~i之间的质因子了
            printf("%d %d\n",i,s); //输出i^s
        }
    }
    if (x>1) printf("%d %d\n",x,1);//最后剩下的x要么是个质数,要么是个1
    puts("");
}

for (int i=2;i<=x/i;i++)

在这个过程中x是不断变小的,当循环到i时,x就不包含2~i-1之间的质数了,因此我们要找的是当前x的大于等于i的质数。

x针对每次for循环下的变化的x,根据性质n中最多只含有一个大于sqrt(n)的质因子,如果这个i大于sqrt(x)了,就可以直接退出了,因为我们已经把x的2到i-1,即2到sqrt(x)之间的质数都求出来了,所以**最后剩下的这个x只有两种情况,**要么是那个大于最开始数据输入时的x的sqrt(x)的唯一质数,要么就是其不存在,剩下的x是个1。

在for循环for (int i=2;i<=x/i;i++)中,

  1. 若x是合数,因为2到i-1之间的质数都不是x的因数了,那i或者大于i的数是他的因数,那么必有x大于i^2,所以循环会继续。

  2. 若x是质数,要么直接因不满足i<=x/i而退出,要么一直执行空操作,循环到不满足i<=x/i而退出。

合数x最终会不断变小,变成大于sqrt(x)的质数x或者1。

3. 筛质数

3.1 埃氏筛

朴素做法:

void getPrimes(int n)//求1到n之间的质数
{
    for (int i=2;i<=n;i++) {
        if(!st[i]) primes[cnt++]=i;
        for (int j=i+i;j<=n;j+=i) st[j]=true; //对于每个i,k*i都是合数,置为true (k>=2) 
    }    
}

但是当一个数是合数,n×m时,就不用再把它的倍数置成合数了,因为早在质数n或者质数m时就已经把n×m的倍速的数置成合数了,是n×m的倍数的数n的倍数的和是m的倍数的数的子集。

埃氏筛:

先初始化,认为所有数都是素数,然后**每找到一个素数x,**把是该素数倍数的合数k*x都归类为合数。

int primes[N],cnt;//存储质数,cnt是下标
bool st[N];//st[i]为false说明i质数

void getPrimes(int n)//求1到n之间的质数
{
    for (int i=2;i<=n;i++) 
        if(!st[i]) {//获得质数i,并把k*i置成合数
            primes[cnt++]=i;
            for (int j=i+i;j<=n;j+=i) st[j]=true; //k*i都是合数,置为true (k>=2) 
        }
}

埃氏筛时间复杂度为O(nlogn),存在一个合数是n×m的情况,在碰到质数n时,在k=m时会把m×n置成合数;当碰到质数m时,在k=n时会把n×m置成合数。二者都是同一个数,即会有重复筛选的情况

3.2 线性筛

线性筛的核心每一个合数只会被它的最小质因数筛掉,因此只会被筛一次。

对于枚举到的每个i,假设它的最小质因数是x,在素数表从primes[0]开始找一直找到x(包括x),在之间的primes[j]都是小于等于x的。满足性质

  1. i%primes[j]==0,则primes[j]==x,它就是i的最小质因数,因此primes[j]必然是primes[j]*i的最小质因数。
  2. i%primes[j]!=0,则primes[j]小于i的所有质因子,因此,也primes[j]必然是primes[j]*i的最小质因数。(因为primes[j]是从小到大枚举的)

当然,对枚举的primes[j]还要有限制,那就是

  1. primes[j]*i必须是小于等于n,这样才能st[primes[j]*i]=true;,防止越界,和判定质数的写法一样,写成primes[j]<=n/i
  2. primes[j]的下标j一定要满足条件j<cnt

事实上限制2不要也行, 我们用到的最大的质数就是质数i本身,而primes数组已经记录了2到i之间的所有质数。

  1. 当i为合数时,它的最小质因子必小于i,而我们的primes数组已经记录了2到i-1之间的所有质数;
  2. 当i为质数时,它的最小质因子就是它本身,我们已经预先把它也放入primes数组了,所以当i为质数时,就是最大遍历的情况,会遍历整个primes数组。

因此,限制2不要也可,但限制1是必须要的,因为primes[j]*i可能越界。

int primes[N],cnt;
bool st[N];//st[i]为false说明i质数
void getPrimes(int n)//求1到n之间的质数
{
    for (int i=2;i<=n;i++) {
        if (!st[i]) primes[cnt++]=i;
        //找i的最小质因子
        for (int j=0;primes[j]<=n/i;j++) {
            st[primes[j]*i]=true;
            if (i%primes[j]==0) break; //找到了最小质因子,后面就不用再筛了,退出
        }
    }
}
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值