文章

【GESP】C++五级真题(埃氏筛思想考点) luogu-B3929 [GESP202312 五级] 小杨的幸运数

GESP C++ 2023年12月五级真题,埃氏筛思想考点,难度⭐⭐⭐☆☆。洛谷难度等级普及/提高−

luogu-B3929 [GESP202312 五级] 小杨的幸运数

题目要求

题目描述

小杨认为,所有大于等于 $a$ 的完全平方数都是他的超级幸运数。

小杨还认为,所有超级幸运数的倍数都是他的幸运数。自然地,小杨的所有超级幸运数也都是幸运数。

对于一个非幸运数,小杨规定,可以将它一直 $+1$,直到它变成一个幸运数。我们把这个过程叫做幸运化。例如,如果 $a=4$,那么 $4$ 是最小的幸运数,而 $1$ 不是,但我们可以连续对 $1$ 做 $3$ 次 $+1$ 操作,使其变为 $4$,所以我们可以说, $1$ 幸运化后的结果是 $4$。

现在,小杨给出 $N$ 个数,请你首先判断它们是不是幸运数;接着,对于非幸运数,请你将它们幸运化。

输入格式

第一行 $2$ 个正整数 $a, N$。

接下来 $N$ 行,每行一个正整数 $x$ ,表示需要判断(幸运化)的数。

输出格式

输出 $N$ 行,对于每个给定的 $x$ ,如果它是幸运数,请输出 lucky,否则请输出将其幸运化后的结果。

输入输出样例 #1

输入 #1
1
2
3
4
5
2 4 
1 
4 
5 
9
输出 #1
1
2
3
4
4 
lucky 
8 
lucky

输入输出样例 #2

输入 #2
1
2
3
4
5
6
7
8
9
10
11
12
16 11 
1 
2 
4 
8 
16 
32 
64 
128 
256 
512
1024
输出 #2
1
2
3
4
5
6
7
8
9
10
11
16 
16 
16 
16 
lucky 
lucky 
lucky 
lucky 
lucky 
lucky 
lucky

说明/提示

样例解释 1

$1$ 虽然是完全平方数,但它小于 $a$,因此它并不是超级幸运数,也不是幸运数。将其进行 $3$ 次 $+1$ 操作后,最终得到幸运数 $4$。$4$ 是幸运数,因此直接输出 lucky

$5$ 不是幸运数,将其进行 $3$ 次 $+1$ 操作后,最终得到幸运数 $8$。

$9$ 是幸运数,因此直接输出 lucky

数据规模

对于 $30\%$ 的测试点,保证 $a,x \le 100,N \le 100$。

对于 $60\%$ 的测试点,保证 $a,x \le 10^6$。

对于所有测试点,保证 $a \le 1,000,000$;保证 $N \le 2 \times 10^5$;保证 $1 \le x \le 1,000,001$。


题目分析

解题思路

本题的关键是快速判定一个数是否为“幸运数”,并对非幸运数求出“幸运化”后的最小值。
幸运数 = 所有 $\ge a$ 的完全平方数(超级幸运数)及其倍数。
数据范围:$a\le 10^6$,$N\le 2\times 10^5$,$x\le 10^6+1$。

下面给出两种可AC的思路(直接暴力会超时),推荐掌握埃式筛思路方法,输入五级考试大纲的内容。


方法一:类埃氏筛标记(前缀数组)

思想:

  1. 先求出 $\ge a$ 的最小完全平方数 $s_0=\lceil\sqrt a\rceil^2$,以及后续所有完全平方数 $s_i=i^2$($i$ 向上取整到 $1001$ 即可覆盖 $10^6+1$)。
  2. 类似素数筛,把每个超级幸运数 $s_i$ 的所有倍数全部打标记,数组 $\texttt{lucky[num]}=1$ 表示 $\texttt{num}$ 是幸运数。
  3. 查询时 $O(1)$ 判 $\texttt{lucky[x]}$ 即可;若 $x$ 未被标记,则从 $x$ 开始向后线性扫描到第一个 $\texttt{lucky[j]}=1$ 的位置,即为幸运化结果。

复杂度:

  • 筛法阶段:超级幸运数个数 $\approx\sqrt{10^6}=10^3$,每个数筛倍数,总操作次数 $\approx 10^3\cdot(10^6/1)=10^9$,在 $10^6$ 范围内实际跑得过(CF/洛谷 $1\,\text s$ 内)。
  • 查询阶段:$O(1)$ 判定 + 最坏 $O(\Delta)$ 向后扫描,$\Delta$ 通常很小(下一个幸运数几乎贴身)。

方法二:数学计算(不预处理筛表)

思想:

  1. 预处理只存“超级幸运数”列表 $\texttt{sq[]}$,元素个数 $\le\sqrt{10^6}=10^3$。
  2. 判定幸运数:枚举 $\texttt{sq[]}$,看是否存在某个 $s\in\texttt{sq}$ 使得 $x\bmod s = 0$,一旦找到立即返回 $\texttt{true}$;否则 $\texttt{false}$。
    • 由于 $\texttt{sq[]}$ 长度 $\le 10^3$,单次判定 $\le 10^3$ 次取模,$2\times 10^5$ 次询问总计算量 $2\times 10^8$,$1\,\text s$ 内可过。
  3. 幸运化:对非幸运数 $x$,求“$\ge x$ 的最小幸运数”。
    分两种情况:
    (1) $x< a$:最小幸运数一定是 $\ge a$ 的最小完全平方数,即 $s_0=\lceil\sqrt a\rceil^2$。
    (2) $x\ge a$:此时只需在超级幸运数集合里找“$\ge x$ 的最小倍数”。
    对每个 $s\in\texttt{sq}$,计算

    \[\texttt{candidate}=s\cdot\Bigl\lceil\frac xs\Bigr\rceil=s+x-x\bmod s\]

    所有 $\texttt{candidate}$ 取最小值即可。
    由于 $\texttt{sq[]}$ 长度 $\le 10^3$,单次幸运化最多 $10^3$ 次计算,$2\times 10^5$ 次总计算量 $2\times 10^8$,同样 $1\,\text s$ 内可过。

优点:

  • 无需 $10^6$ 级别大数组,内存 $O(\sqrt a)$;
  • 极限数据下运行时间稳定,不会被卡。

缺点:

  • 单次查询常数比方法一略大;
  • 代码量稍多,需要小心处理边界($x<a$、$x=a$、整除等)。


示例代码

方法一、类素数埃式筛思路

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#include <cmath>
#include <iostream>

int lucky_nums[1002100];

int main() {
    int a, N;
    std::cin >> a >> N;  // 读入起始完全平方数下界 a 与询问次数 N

    // 埃氏筛思想:标记所有超级幸运数(≥a 的完全平方数)及其倍数
    for (int i = std::ceil(std::sqrt(a)); i <= 1001; i++) {
        int square_num = i * i;          // 当前超级幸运数
        lucky_nums[square_num] = 1;      // 标记自己
        for (int j = 2; j * square_num <= 1002000; j++) {
            lucky_nums[j * square_num] = 1;  // 标记其所有倍数
        }
    }

    // 处理 N 次询问
    for (int i = 0; i < N; i++) {
        int x;
        std::cin >> x;
        if (lucky_nums[x]) {             // 若 x 已是幸运数
            std::cout << "lucky" << std::endl;
        } else {
            int max_n = std::max(x, a);  // 从 max(x,a) 开始往后找第一个幸运数
            for (int j = max_n; j <= 1002100; j++) {
                if (lucky_nums[j]) {
                    std::cout << j << std::endl;  // 输出幸运化结果
                    break;
                }
            }
        }
    }

    return 0;
}

方法二、数学计算法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
#include <cmath>
#include <iostream>
#include <vector>

std::vector<int> square_nums;  // 存放所有 ≥a 的完全平方数(超级幸运数)

// 判断 x 是否为幸运数:只要 x 是某个超级幸运数的倍数即可
bool is_lucky(int x) {
    for (int i = 0; i < square_nums.size(); i++) {
        if (x == square_nums[i] || x % square_nums[i] == 0) {
            return true;  // 找到任一超级幸运数能整除 x,即为幸运数
        }
    }
    return false;  // 没有任何超级幸运数能整除 x,不是幸运数
}

// 对非幸运数 x 进行“幸运化”:返回不小于 x 的最小幸运数
std::string luckyize(int x, int a) {
    // 若 x 本身已是幸运数且 ≥a,直接返回 lucky
    if (is_lucky(x) && x >= a) {
        return "lucky";
    }

    int lucky_num;  // 用于保存幸运化结果
    if (a >= x) {
        // 当 a ≥ x 时,最小幸运数就是 ≥a 的最小完全平方数
        lucky_num = std::pow(std::ceil(std::sqrt(a)), 2);
    } else {
        // 当 x > a 时,找 ≥x 的最小“超级幸运数倍数”
        // 先以第一个超级幸运数为基准,计算其倍数中 ≥x 的最小值
        // 计算 ≥x 的最小“square_nums[0] 的倍数”:
        // 若 x 能被 square_nums[0] 整除,则 x 本身就是该倍数;
        // 否则先求出 x 除以 square_nums[0] 的余数 r = x % square_nums[0],
        // 再把 x 向上“补齐”到下一个整数倍,即 x + (square_nums[0] - r)。
        // 合并写法:square_nums[0] + x - x % square_nums[0]
        lucky_num = square_nums[0] + x - x % square_nums[0];
        // 再遍历其余超级幸运数,取最小值
        for (int i = 1; i < square_nums.size(); i++) {
            lucky_num = std::min(lucky_num, square_nums[i] + x - x % square_nums[i]);
        }
    }
    return std::to_string(lucky_num);  // 返回字符串形式的幸运化结果
}

int main() {
    int a, N;
    std::cin >> a >> N;  // 读入起始下界 a 和询问次数 N

    // 预处理:把所有 ≥a 的完全平方数加入 square_nums
    for (int i = 1; i <= 1001; i++) {
        if (i * i >= a) {
            square_nums.push_back(i * i);
        }
    }

    // 处理 N 次询问
    for (int i = 0; i < N; i++) {
        int x;
        std::cin >> x;  // 读入待判断/幸运化的数
        std::cout << luckyize(x, a) << std::endl;  // 输出结果
    }

    return 0;
}

所有代码已上传至Github:https://github.com/lihongzheshuai/yummy-code

GESP 学习专题站:GESP WIKI

luogu-”系列题目可在洛谷题库进行在线评测。

bcqm-”系列题目可在编程启蒙题库进行在线评测。

欢迎加入Java、C++、Python技术交流QQ群(982860385),大佬免费带队,有问必答

欢迎加入C++ GESP/CSP认证学习QQ频道,考试资源总结汇总

欢迎加入C++ GESP/CSP学习交流QQ群(688906745),考试认证学员交流,互帮互助

GESP/CSP 认证学习微信公众号
GESP/CSP 认证学习微信公众号
本文由作者按照 CC BY-NC-SA 4.0 进行授权