质数距离 【全网最详细思路+代码注释!看完必懂不懂你打我!】

思路:

  1. 题目给定的是区间 [l, r],让你筛选出里面所有满足性质的质数,区间里既有质数又有合数,假如只有质数,则可以直接进行暴力枚举,因为区间的长度也就一百万级别。
  2. 但是,由于区间既有质数又有合数,所以我们需要筛掉区间里面的合数,进行标记删除。
  3. 那么如何删除合数呢?根据算术基本定理可知:任何一个大于等于0的正整数都可以表示成若干个质数的乘积形式。所以说,筛去合数,我们选择使用质数去标记它在 [l, r]里的倍数。
  4. 而我们所学的筛质数是从 [1, n]的,并不能按照区间去筛,所以应该从 1 ~ n 去筛质数,但是,本题的n即为 r,r 最大取值为:231-1。所以会超时,应该想办法缩减筛质数的区间。
  5. 对于区间里每个合数,我们是需要它的因子去判断它,所以如果我们只记录它的最小的一个因子不就可以了,然后又由(3)可知,用质数去筛区间内的合数,所以组合起来就是最小质因子,所以我们应该缩减最小质因子的区间。将合法范围的最小质因子存储起来,然后去筛区间内的合数。
  6. 缩减质数的区间:重要性质:对于任何一个合数而言,一个合数的最小质因子,必定是小于等于该合数开根号。所以区间里面的每个数而言,其最小质因子也是小于等于对应元素的开根号。 该性质常常可以将O(n)的枚举转化为:O(根号n) 的枚举。由于我们的 n ,即区间右端点,最大取到
    231-1,所以说对于 r 而言,必然存在一个 最小质因数:小于等于 r开根号。而 231-1开根号的数量级大概是 4万多,所以为了保险起见,我们只需要求出 1~5万的所有质数就行了。因为区间内的合数的最小质因子也必然在该范围内。
    在这里插入图片描述
  7. 筛出所有的质数后,再枚举每个质数,用质数去筛区间里的每个合数。但是,你怎么直到里面的合数是当前质数的多少倍呢?你只能确定你有里面合数的最小质因子。所以我们需要去枚举每个质数,对于一个质数p,它从几倍开始枚举,取决于它大于等于L的最小的p的倍数。所以我们可以先看L是p的几倍,若能整除,则从L/p倍开始,第一个筛的就是L。若不能整除,则只需要往上加一倍就可以啦,即向上取整,而C++里面默认是向下取整,所以这里我们应该是采用公式:{L+p-1/p * p};开始枚举,直到质数 * 对应的倍数 > n为止。
  8. 然后通过枚举区间里没有被标记的质数,将所有的质数都存到一个数组里面去,然后开始两两枚举最小差值,最大差值。

草稿:(不用看,舍不得删除!)

本题给定的区间长度最大为1e6,即为U-L的最大长度!但是U和L的取值可以很大为:231-1,由于涉及到筛质数的操作,所以我们不可能筛到231-1,筛法是从2~n筛,所以说会超时。而我们应该抓住一个核心的性质:任何一个合数N都存在一个小于等于其根号N的因子存在,(证明如图一所示),而对于一个数,假设U=231-1,那么它也必然存在一个因子,该因子的范围是:<1, 231/2> == <1, 215>大概十万左右。那么我们可以将1 ~ 1e5内的所有质数筛选出来,此时对于区间 [L, U]里的任何一个合数而言,都必然有一个因子是小于等于1e5的,因为其最大取值的因子都小于等于1e5了。因此对于[L, U]里面的任何一个合数都至少存在一个质因子在[1, 1e5] 之间。

  1. 先预处理出来5e4以内的质数筛选出来。
  2. 然后利用筛选出来的质数,将[l, r]区间内的所有合数筛去,因为如果区间[l, r]里面有合数的话,那么其质因子的范围必然在[1, 5e4]之间,由于区间右端点 r 的最大值为 231-1,对于 231-1其必然也存在一个小于等于 根号(231-1)的因子,范围在5e4内,那么对于其区间内的数的质因子也必然是在5e4以内了。
  3. 时间复杂度的分析:假设 [l, r]的范围是1e6的话,而p的倍数最多是:[区间长度1e6]/p;而每个质数都要枚举一遍,那么整个加起来的时间复杂度:1e6*(1/2 + 1/3 + 1/5 + 1/7 + …+1/n1/2);大约整个加起来是:所以括号内的时间复杂度为log(log(n1/2))级别的,该时间复杂度也在埃氏筛法里面出现过!
  4. 所以总的来看:1e6 * 3 = O(n*loglog(n1/2));
  5. 常用性质:一个数的最小质因子一定是 小于等于n1/2从而将O(n)变为O(n1/2);

总结:

技巧:C++里面两个数相除上取整:

  1. 采用函数ceil:
  2. 采用公式:比如找一个数:该倍数必须是大于等L的最小的p的倍数,那么先用L/p算出至少多少倍,然后往上取整:[(L/p)*p]上取整 = [(L+p-1)/p] * p;
    1.当L是p的倍数时,+p-1相当于没加。和之前相等,
    2.当L不是p的倍数时,L%p >= 1,再加上一个p-1,必然会多一。

代码:

#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

const int N = 1e6 + 10;
typedef long long LL;
int primes[N];  //存储质数数组
bool st[N];     //标记是否是合数,为真的话就是合数!
int cnt;

void get_primes(int n)
{
    memset(st, 0, sizeof st);
    cnt=0;
    for (int i=2; i <= n; i ++)
    {
        if (!st[i]) primes[cnt ++] = i; //存储素数
        for (int j=0; primes[j]*i <= n; j ++)   //枚举每个质数
        {
            st[primes[j]*i] = true;     //标记每个质数
            if (i%primes[j]==0) break;  //保证每个合数只会被它的最小质因子筛去。
        }
    }
}

int main()
{
    int l, r;
    
    while (cin >> l >> r)
    {
        get_primes(50000);
        memset (st, 0, sizeof (st));    //因为后续用来标记删除区间里的合数。
        
        //既然质数已经预处理出来了,那么就开始筛去区间里面的合数:
        for (int i=0; i < cnt; i ++)
        {
            LL p = primes[i];
            //取max的原因是防止出现p。p是p的倍数,但不在[l,r]范围内?
            for (LL j = max((l+p-1)/p*p, 2*p); j <= r; j += p)  //枚举质数p的各个倍数!
            {
                st[j-l] = true; //设置偏移量,因为l过于庞大了,数组下标可能取不到。
            }
        }
        
        cnt = 0;    //重置,因为预处理的primes数组已经用完了,可以丢了!存储区间[l,r]内的所有质数
        for (int i=0; i <= r-l; i ++)
        {
            if (!st[i] && i+l>=2) //如果这个数是质数的话,且不为1,因为1既不是质数也不是合数
                primes[cnt ++ ] = i + l;
        }
        
        if (cnt < 2) puts("There are no adjacent primes.");
        else
        {
            int minp = 0, maxp = 0;
            for (int i = 0; i + 1 < cnt; i ++ )
            {
                int d = primes[i + 1] - primes[i];
                if (d < primes[minp + 1] - primes[minp]) minp = i;
                if (d > primes[maxp + 1] - primes[maxp]) maxp = i;
            }
            
            printf("%d,%d are closest, %d,%d are most distant.\n",
                primes[minp], primes[minp + 1],
                primes[maxp], primes[maxp + 1]);
            
        }
    }
    
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值