惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

J
Java Code Geeks
Martin Fowler
Martin Fowler
MongoDB | Blog
MongoDB | Blog
博客园 - Franky
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
Microsoft Security Blog
Microsoft Security Blog
H
Hackread – Cybersecurity News, Data Breaches, AI and More
博客园_首页
腾讯CDC
D
Docker
The Cloudflare Blog
量子位
爱范儿
爱范儿
L
LangChain Blog
博客园 - 三生石上(FineUI控件)
博客园 - 司徒正美
aimingoo的专栏
aimingoo的专栏
Blog — PlanetScale
Blog — PlanetScale
Jina AI
Jina AI
Apple Machine Learning Research
Apple Machine Learning Research
Hugging Face - Blog
Hugging Face - Blog
博客园 - 聂微东
Vercel News
Vercel News
MyScale Blog
MyScale Blog

OneCoder

【GESP】C++三级、四级练习 luogu-P2089 烤鸡 【NOIP】1997真题解析 luogu-P2241 统计方形(数据加强版) GESP三、四级以上可练习 【GESP】C++四级练习 luogu-P1296 奶牛的耳语 【CSP】CSP-J 2022 第一轮真题解析(三):完善程序题 【GESP】C++五级练习 luogu-P3743 小鸟的设备 【CSP】CSP-J 2022 第一轮真题解析(二):阅读程序题 【CSP】CSP-J 2022 第一轮真题解析(一):单项选择题 【CSP】CSP-J 2023 第一轮真题解析(三):完善程序题 【CSP】CSP-J 2023 第一轮真题解析(二):阅读程序题 【GESP】C++五级练习 luogu-P1678 烦恼的高考志愿 【CSP】CSP-J 2023 第一轮真题解析(一):单项选择题 【CSP】CSP-J 2024 第一轮真题解析(三):完善程序题 【CSP】CSP-J 2024 第一轮真题解析(二):阅读程序题 【CSP】CSP-J 2024 第一轮真题解析(一):单项选择题 【NOIP】2015真题解析 luogu-P2678 跳石头(适合GESP六级以上练习) 【GESP】C++六级练习 luogu-B2174, 完全背包 【NOIP】2005真题解析 luogu-P1048 采药(适合GESP六级以上练习) 【GESP】C++六级真题 luogu-P17013, [GESP202606 六级] 满二叉树 【GESP】C++六级真题 luogu-P17012, [GESP202606 六级] 条形蛋糕 【GESP】C++五级真题 luogu-P17011 [GESP202606 五级] 晚宴 【GESP】C++五级真题 luogu-P17010 [GESP202606 五级] 排排坐 【GESP】C++四级真题 luogu-B4558 [GESP202606 四级] 身高体重指数 【GESP】C++四级真题 luogu-B4557 [GESP202606 四级] 扫雷 【GESP】C++三级真题 luogu-B4556 [GESP202606 三级] 字符转换 【GESP】C++三级真题 luogu-B4555 [GESP202606 三级] 加密 【GESP】C++二级真题 luogu-B4554 [GESP202606 二级] 菱形 【GESP】C++二级真题 luogu-B4553 [GESP202606 二级] 完全平方数计数 【GESP】C++一级真题 luogu-B4551 [GESP202606 一级] 去旅行 【GESP】C++一级真题 luogu-B4552 [GESP202606 一级] 交税 【NOIP】2000真题解析 luogu-P1023 税收与补贴问题(适合GESP四、五级以上练习)
【CSP】CSP-J 2022真题 解密 luogu-P8814 (适合GESP四级及以...
OneCoder · 2026-05-31 · via OneCoder

CSP-J 2022真题-解密,是一道结合数学推导与数值运算的经典题目。本题可以通过将方程组转化为一元二次方程,利用求根公式在 $O(1)$ 的时间内求解;也可以利用单调性通过二分法进行求解。适合 GESP 四级及以上考生练习,难度⭐⭐,洛谷难度等级普及-

P8814 [CSP-J 2022] 解密

题目要求

题目描述

给定一个正整数 $k$,有 $k$ 次询问,每次给定三个正整数 $n_i, e_i, d_i$,求两个正整数 $p_i, q_i$,使 $n_i = p_i \times q_i$、$e_i \times d_i = (p_i - 1)(q_i - 1) + 1$。

输入格式

第一行一个正整数 $k$,表示有 $k$ 次询问。

接下来 $k$ 行,第 $i$ 行三个正整数 $n_i, d_i, e_i$。

输出格式

输出 $k$ 行,每行两个正整数 $p_i, q_i$ 表示答案。

为使输出统一,你应当保证 $p_i \leq q_i$。

如果无解,请输出 NO

输入输出样例 #1

输入 #1

1
2
3
4
5
6
7
8
9
10
11
10
770 77 5
633 1 211
545 1 499
683 3 227
858 3 257
723 37 13
572 26 11
867 17 17
829 3 263
528 4 109

输出 #1

1
2
3
4
5
6
7
8
9
10
2 385
NO
NO
NO
11 78
3 241
2 286
NO
NO
6 88

说明/提示

【数据范围与约定】

以下记 $m = n - e \times d + 2$。

保证对于 $100\%$ 的数据,$1 \leq k \leq {10}^5$,对于任意的 $1 \leq i \leq k$,$1 \leq n_i \leq {10}^{18}$,$1 \leq e_i \times d_i \leq {10}^{18}$,$1 \leq m \leq {10}^9$。

测试点编号$k \leq$$n \leq$$m \leq$特殊性质
$1$$10^3$$10^3$$10^3$保证有解
$2$$10^3$$10^3$$10^3$
$3$$10^3$$10^9$$6\times 10^4$保证有解
$4$$10^3$$10^9$$6\times 10^4$
$5$$10^3$$10^9$$10^9$保证有解
$6$$10^3$$10^9$$10^9$
$7$$10^5$$10^{18}$$10^9$保证若有解则 $p=q$
$8$$10^5$$10^{18}$$10^9$保证有解
$9$$10^5$$10^{18}$$10^9$
$10$$10^5$$10^{18}$$10^9$

题目分析

本题给出了包含两个未知数 $p$ 和 $q$ 的方程组,要求我们根据已知的 $n, e, d$ 求解符合条件的 $p$ 和 $q$。由于数据范围中 $k \le 10^5$,如果使用暴力枚举因数的方式求解,时间复杂度会达到 $O(k \sqrt{n})$,显然会超时。因此我们需要更高效的求解方法。

1. 代数变形与方程组构建

首先,我们对方程组进行展开与化简。

已知方程组为:

\[\begin{cases} n = p \times q & \text{①} \\ e \times d = (p - 1)(q - 1) + 1 & \text{②} \end{cases}\]

展开方程 ②:

\[e \times d = p \times q - p - q + 1 + 1\] \[e \times d = p \times q - (p + q) + 2\]

将方程 ① 中的 $n = p \times q$ 代入上式:

\[e \times d = n - (p + q) + 2\]

变形可得 $p + q$ 的表达式:

\[p + q = n - e \times d + 2\]

为了书写简便,我们令 $m = p + q = n - e \times d + 2$。结合题目已有的方程,我们可以构建一个新的方程组:

\[\begin{cases} p + q = m \\ p \times q = n \end{cases}\]

这变成了经典的“已知两数之和与两数之积,求解这两数”的问题。


2. 解法一:一元二次方程求根公式(推荐)

根据韦达定理,$p$ 和 $q$ 可以看作是关于 $x$ 的一元二次方程的两个实数根:

\[x^2 - m x + n = 0\]

利用求根公式,可得:

\[x = \frac{m \pm \sqrt{m^2 - 4n}}{2}\]

因为题目要求 $p \le q$,所以:

\[p = \frac{m - \sqrt{m^2 - 4n}}{2},\quad q = \frac{m + \sqrt{m^2 - 4n}}{2}\]

求解时的判定与注意事项:
  1. 无实数解判定: 若判别式 $\Delta = m^2 - 4n < 0$,方程无实数解,输出 NO
  2. 整数解判定
    • 判别式 $\Delta$ 必须是完全平方数。我们可以计算 $r = \lfloor \sqrt{\Delta} \rfloor$(或使用四舍五入 round(sqrt(delta))),并校验是否满足 $r \times r == \Delta$。如果不满足,说明 $\sqrt{\Delta}$ 不是整数,即无整数解。
    • 分子 $m - r$ 和 $m + r$ 必须是偶数,即满足 $(m - r) \pmod 2 == 0$。如果不能整除,也说明无整数解。
  3. 数据溢出预防: 虽然提示中 $m \le 10^9$,但 $m^2$ 的范围可达 $10^{18}$,$n$ 的范围可达 $10^{18}$。在 C++ 中,我们必须使用 long long 类型来承载所有的计算,以防止数值溢出。

该方法单次查询的时间复杂度为 $O(1)$,总时间复杂度为 $O(k)$,执行效率非常高。


3. 解法二:二分答案法

如果不想使用浮点数开方函数 sqrt(),也可以利用单调性采用二分查找。

由于 $p + q = m$,且 $p \le q$,我们可以确定 $p$ 的取值范围在 $[1, \lfloor m / 2 \rfloor]$ 之间。

在这段区间内,随着 $p$ 的增大,乘积 $p \times (m - p)$ 是严格单调递增的。 因此,我们可以在 $[1, m/2]$ 内二分查找 $p$:

  • 设当前二分区间的中点为 mid,计算乘积 val = mid * (m - mid)
  • val == n,说明找到了符合条件的整数解,此时 $p = \mathrm{mid}$,$q = m - \mathrm{mid}$。
  • val < n,说明当前的 mid 偏小,应向右半区间继续查找,令 L = mid + 1
  • val > n,说明当前的 mid 偏大,应向左半区间继续查找,令 R = mid - 1

二分查找每次询问的时间复杂度为 $O(\log m)$,总时间复杂度为 $O(k \log m)$。在 $k = 10^5, m = 10^9$ 的情况下,总计算次数约为 $3 \times 10^6$ 次,完全可以在时限内通过。


示例代码

方法一:求根公式法($O(1)$)

以下是使用一元二次方程求根公式实现的 C++ 代码。

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
#include <iostream>
#include <cmath>

void solve() {
    long long n, d, e;
    std::cin >> n >> d >> e;

    // 根据推导,m = p + q
    long long m = n - e * d + 2;

    // 计算判别式 delta = m^2 - 4n
    long long delta = m * m - 4 * n;

    // 如果判别式小于 0,无实数解
    if (delta < 0) {
        std::cout << "NO\n";
        return;
    }

    // 对方程求根,并校验是否为完全平方数
    long long r = std::round(std::sqrt(delta));
    if (r * r != delta) {
        std::cout << "NO\n";
        return;
    }

    // 校验分子是否为偶数,即是否能被 2 整除
    if ((m - r) % 2 != 0) {
        std::cout << "NO\n";
        return;
    }

    // 计算 p 和 q
    long long p = (m - r) / 2;
    long long q = (m + r) / 2;

    // 校验 p 和 q 是否为正整数
    if (p <= 0 || q <= 0) {
        std::cout << "NO\n";
        return;
    }

    std::cout << p << " " << q << "\n";
}

int main() {
    // 优化输入输出流性能
    std::ios::sync_with_stdio(false);
    std::cin.tie(nullptr);

    int k;
    std::cin >> k;
    while (k--) {
        solve();
    }

    return 0;
}

方法二:二分答案法($O(\log m)$)

以下是使用二分查找实现的 C++ 代码。

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
#include <iostream>

void solve() {
    long long n, d, e;
    std::cin >> n >> d >> e;

    // 根据推导,m = p + q
    long long m = n - e * d + 2;

    // 二分区间 p <= q,且 p + q = m,因此 p 必然在 [1, m / 2]
    long long L = 1, R = m / 2;
    long long p = -1;

    while (L <= R) {
        long long mid = L + (R - L) / 2;
        long long val = mid * (m - mid);

        if (val == n) {
            p = mid;
            break; // 找到答案,退出二分
        } else if (val < n) {
            L = mid + 1; // 乘积偏小,增大 p
        } else {
            R = mid - 1; // 乘积偏大,减小 p
        }
    }

    // 如果没有找到解
    if (p == -1) {
        std::cout << "NO\n";
    } else {
        long long q = m - p;
        std::cout << p << " " << q << "\n";
    }
}

int main() {
    // 优化输入输出流性能
    std::ios::sync_with_stdio(false);
    std::cin.tie(nullptr);

    int k;
    std::cin >> k;
    while (k--) {
        solve();
    }

    return 0;
}


结语

本题的核心突破口在于对方程组的展开和化简。通过代数变形,将复杂的乘积方程转化为了已知和与积的经典二次方程 model。在实战中,熟练掌握这类代数变形技巧以及边界和精度的合理校验,是快速且准确地解决数学类算法题目的关键。

所有代码已上传至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 认证学习微信公众号