You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

7.2 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

##AcWing 196. 质数距离

一、题目描述

给定两个整数 LU,你需要在闭区间 [L,U] 内找到距离最接近的两个相邻质数 C_1C_2(即 C_2C_1 是最小的),如果存在相同距离的其他相邻质数对,则输出第一对。

同时,你还需要找到距离最远的两个相邻质数 D_1D_2(即 D_1D_2 是最大的),如果存在相同距离的其他相邻质数对,则输出第一对。

输入格式

每行输入两个整数 LU,其中 LU 的差值不会超过 10^6

输出格式

对于每个 LU,输出一个结果,结果占一行。

结果包括距离最近的相邻质数对和距离最远的相邻质数对。(具体格式参照样例)

如果 LU 之间不存在质数对,则输出 There are no adjacent primes.。

数据范围

1≤L<U≤2^{31}1

输入样例

2 17
14 17

输出样例

2,3 are closest, 7,11 are most distant.
There are no adjacent primes.

二、二次筛法

这里的区间范围给定的最大值是2^{31} - 1,而用线性筛法求的是[1,n]中的所有质数,因此直接用线性筛法求肯定会直接挂掉,因此需要通过挖掘某些性质,才能完成。

1.挖掘性质

  • 性质1 若一个数n是一个合数,必然存在2个因子d,\large \frac{n}{d},假设d <=\frac{n}{d},则d <= \sqrt{n},因此必然存在一个小于等于 \sqrt{n}的因子

  • 性质2x∈[L,R],且x是合数,则一定存在P <= \sqrt{2^{31}-1} (< 50000),使得P能整除x,其中P < x\sqrt{2147483647}=46340,这个值是小于50000的,所以,yxc老师一般喜欢使用直接写上上限值50000,就是因为这个数字够用,而且够大,好记。

2.实现步骤

  • 找出1 \sim \sqrt{2^{31}-1} (< 50000)中的所有质因子
  • 对于1 \sim 50000 中每个质数P,将[L,R]中所有P的倍数筛掉(至少2倍,1倍的就是自己,是质数不是合数) 找到大于等于L的最小的P的倍数P_0,找下一个倍数时只需要+= P即可

3.引理

寻找大于L的第一个P的倍数时,采用了下面的引理: 引理(分数的上取整转换下取整)

4.细节

Q: 每个质数是2\sim 50000中的数,为啥 LL p = primes[i]; 这里的pLL啊, int 不就够了吗? A: LL p = primes[i],这里pLL是因为如果p也是用int类型,本身l也是用int类型,如果l取得足够大,下面的l + p - 1会有可能直接爆int 变成负数

Q: for (LL j = max(p * 2, (l + p - 1) / p * p); j <= r; j += p) 这里的 j 是小于等于 r 的,而 r 的取值范围是小于2 ^ {31},也是在int 范围内啊, 这里为啥用LL啊? A: 这里的 j 是小于等于 r 的,而 r 的取值范围是小于2 ^ {31},这里确实是这样,可是这个循环跳出的条件是j <= r,也就是说如果r是最大的int,那么当j += p,要超过最大的int的时候需要比它还大才能跳出循环,因此直接爆int变成负数,然后j <= r依然成立,会一直死循环下去。其实本质上这个问题与问题1是一回事,在做数论时一次要小心LLint的加法、乘法,小心爆int是一条死规则,尽量多想想是不是应该用LL来设置变量~

Q: 为什么要写上常数50000呢? A:

  • 写法1: get_primes(50000)
  • 写法2: get_primes(sqrt(r))
  • 写法3: get_primes(sqrt(INT32_MAX))
    其实都行,但有了经验后,发现,直接写50000最简单粗暴效果好~

Q: 为什么要使用偏移量st[j - l] = true? A: 因为数据范围太大,直接用数组进行桶计数,会超空间,需要离散化一下,就是把1e6范围内的数据映射到0\sim 1e6

Q:为什么for (int i = 1; i < cl; i++) 这里要用cl-1呢?为什么不是cl呢? A:cl个质数,下标是[1,cl],因为现在枚举的是前一个质数,要保证还有后一个,所以取不到cl

5.时间复杂度

O(n)

三、实现代码

#include <bits/stdc++.h>
using namespace std;
typedef long long LL;

// 线性筛
const int N = 1e6 + 10;
int primes[N], cnt;
bool st[N];
void get_primes(int n) {
    memset(primes, 0, sizeof primes);
    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;
        }
    }
}

bool b[N];    // 记录区间内有哪些数是合数
int c[N], cl; // 区间内有哪些质数
int l, r;     // 区间范围

int main() {
    // 加快读入
    ios::sync_with_stdio(false), cin.tie(0);

    // 一次性筛质数
    get_primes(50000);

    while (cin >> l >> r) {
        memset(b, 0, sizeof b); // 清除合数数组
        memset(c, 0, sizeof c); // 清除质数数组
        cl = 0;

        // 标识质数的倍数,也就是合数有哪些
        for (int i = 0; i < cnt; i++) {
            LL p = primes[i];
            for (LL j = max(p * 2, (l + p - 1) / p * p); j <= r; j += p) // 枚举p的2倍以上倍数最小要比l大最大不能超过r
                b[j - l] = true;                                         // 采用偏移量记录剔除标识
        }

        // Hack data: 1 100
        // 输出: 1,2 are closest, 89,97 are most distant.
        // 答案: 2,3 are closest, 89,97 are most distant.
        // 原因: 下面的逻辑判断b[i]是否等于0表示i+l不是某个质数的倍数合数也就是是合数但这样判断是有问题的因为考虑边界问题
        // 1通过上面的代码是无法剔除掉的也就是说上面的剔除的是合数但数字除了合数可不是只有质数还有一个特殊的数字11即不是质
        // 数也不是合数,这句话可不是说说而已。
        for (int i = 0; i <= r - l; i++)
            if (!b[i] && i + l > 1) // 如果不是合数并且不是1才是质数
                c[++cl] = i + l;    // 区间内的质数多了一个i+l质数

        if (cl < 2)
            puts("There are no adjacent primes.");
        else {
            int mi = 1, mx = 1;            // 第一只猴子就是大王
            for (int i = 1; i < cl; i++) { // 范围是1~cl,而此处因为要枚举的是每个当前i与后一个的关系所以需要枚举到cl-1
                int d = c[i + 1] - c[i];
                if (d < c[mi + 1] - c[mi]) mi = i;
                if (d > c[mx + 1] - c[mx]) mx = i;
            }
            printf("%d,%d are closest, %d,%d are most distant.\n", c[mi], c[mi + 1], c[mx], c[mx + 1]);
        }
    }
    return 0;
}