【CSP】CSP-J 2019 江西真题 | 次大值 luogu-P5682 (适合GESP四、五级及以上考生练习)

四季读书网 2 0
【CSP】CSP-J 2019 江西真题 | 次大值 luogu-P5682 (适合GESP四、五级及以上考生练习)
【CSP】CSP-J 2019 江西真题 | 次大值 luogu-P5682 (适合GESP四、五级及以上考生练习) 第1张科普类资源清单【CSP】CSP-J 2019 江西真题 | 次大值 luogu-P5682 (适合GESP四、五级及以上考生练习) 第2张
信奥业余科普

【CSP】CSP-J 2019 江西真题 | 次大值 luogu-P5682 (适合GESP四、五级及以上考生练习) 第3张【CSP】CSP-J 2019 江西真题 | 次大值 luogu-P5682 (适合GESP四、五级及以上考生练习) 第4张GESP学习资源清单【CSP】CSP-J 2019 江西真题 | 次大值 luogu-P5682 (适合GESP四、五级及以上考生练习) 第5张【CSP】CSP-J 2019 江西真题 | 次大值 luogu-P5682 (适合GESP四、五级及以上考生练习) 第6张

真题
练习题
考纲解析
一级真题解析一级练习题清单【CSP】CSP-J 2019 江西真题 | 次大值 luogu-P5682 (适合GESP四、五级及以上考生练习) 第7张111-8级全考纲解析
二级真题解析二级练习题清单GESP/CSP必备技能
三级真题解析三级练习题清单考纲解密
四级真题解析四级练习题清单资源汇总/经验交流
五级真题解析五级练习题清单
六级真题解析六级练习题清单


【CSP】CSP-J 2019 江西真题 | 次大值 luogu-P5682 (适合GESP四、五级及以上考生练习) 第8张【CSP】CSP-J 2019 江西真题 | 次大值 luogu-P5682 (适合GESP四、五级及以上考生练习) 第9张CSP学习资源清单【CSP】CSP-J 2019 江西真题 | 次大值 luogu-P5682 (适合GESP四、五级及以上考生练习) 第10张【CSP】CSP-J 2019 江西真题 | 次大值 luogu-P5682 (适合GESP四、五级及以上考生练习) 第11张

CSP-XL
CSP-J
2025辽宁CSP-XL复赛真题解析CSP-J 真题解析

【CSP】CSP-J 2019 江西真题 | 次大值 luogu-P5682 (适合GESP四、五级及以上考生练习) 第12张【CSP】CSP-J 2019 江西真题 | 次大值 luogu-P5682 (适合GESP四、五级及以上考生练习) 第13张NOI学习资源清单【CSP】CSP-J 2019 江西真题 | 次大值 luogu-P5682 (适合GESP四、五级及以上考生练习) 第14张【CSP】CSP-J 2019 江西真题 | 次大值 luogu-P5682 (适合GESP四、五级及以上考生练习) 第15张
NOIP
1998年真题解析‍‍
1999年真题解析
2001年真题解析‍‍
2011年真题解析

CSP-J 2019江西省真题- 次大值,主要考察排序算法与取模运算的数学性质,重点在于对不同数据的分情况讨论与逻辑推导分析,适合GESP四、五级及以上考生练习,难度⭐⭐⭐,洛谷难度等级普及/提高-

P5682 [CSP-J 2019 江西] 次大值

题目要求

题目描述

Alice 有  个正整数,数字从  编号,分别为 Bob 刚学习取模运算,于是便拿这  个数进行练习,他写下了所有

的值,其中  表示取模运算。

Alice 想知道所有的结果中,严格次大值是多少。将取模后得到的所有值进行去重,即相同的结果数值只保留一个,剩余数中第二大的值就称为严格次大值。

输入格式

第一行一个正整数 ,表示数字个数。第二行  个正整数表示 

输出格式

仅一行一个整数表示答案。若取模结果去重后剩余数字不足两个,则输出 

输入输出样例 #1

输入 #1
44 5 5 6
输出 #1
4

输入输出样例 #2

输入 #2
41 1 1 1
输出 #2
-1

输入输出样例 #3

输入 #3
712 3 8 5 7 20 15
输出 #3
12

说明/提示

【数据范围】

对于  的数据,对于  的数据,对于  的数据,

【样例 1 解释】

所有取模的结果为 去重后有:,结果为 


题目分析

本题是一道非常经典的数学思维与算法题,乍看似乎需要两两取模求解(复杂度为 ),但  最大为 ,如果用暴力枚举法必然会超时(Time Limit Exceeded)。因此我们需要挖掘取模计算背后的数学规律

数学规律分析:

当我们求两个数互相取模  时,有以下几种情况:

  1. 若 ,则 。(这就是说小数字对大数字取模,结果等于小数字本身)
  2. 若 ,则 。(结果一定严格小于除数 

因为要找“去重后”的次大值,我们可以将原数组首先进行排序并在逻辑上去重。假设去重后不同元素的序列按升序排列为 

  • 最大值是怎么产生的?根据第 1 点,要想结果最大,就让最大数字去充当除数,也就是利用 。 也就是说,整个集合所有取模组合中最大的那个值,必然是原序列中严格第二大的数字(即 )。

  • 严格次大值怎么产生?既然绝对最大值是 ,那么有资格成为次大值的,只有可能是下面两个候选者:

    因此,严格次大值必然是 

    1. 原始序列中严格第三大的数字,也就是利用  产生。
    2. 最大的数字对第二大数字取余的结果:。这一项有时候也会意外地比较大。

综合以上分析,我们需要根据唯一数字的不同个数进行分类讨论:

  • 当  时:也就是所有数字是一样的,任意两个取模必定为 。此时去重后结果不足  种,按题意要求输出 -1
  • 当  时:最大的取模结果是  (由  得来);而严格次大的结果一定是由大数除小数得来,即 
  • 当  时:根据我们前文的分析,次大值为 (如果将数组切换为以  起始下标的表示)。

示例代码

解法一:降序遍历寻找最大的三个不同数字

按照上述推论,我们可以先对全体数字进行排序,然后从大到小依次找到最大的三个不同的互异数字再进行分类讨论结算。

#include<algorithm>#include<iostream>// 全局数组,防止申请大空间时引发内存溢出问题(栈溢出)int ary[200005];intmain(){    int n;    std::cin >> n;    // 读入输入数据    for (int i = 0; i < n; i++) {        std::cin >> ary[i];    }    // 对数组进行升序排序,时间复杂度 O(N log N)    std::sort(ary, ary + n);    int count_diff = 1// 记录找到了多少个【不同的数字】    int big_one = ary[n - 1]; // 预设数组最后一个元素(排序后全局最大的数字)    int big_two = 0, big_thr = 0// 用来存第二大(次大)和第三大的数字    // 因为排序过了,我们从后往前遍历寻找严格的次大和第三大数字即可    for (int i = n - 2; i >= 0; i--) {        // 如果已经成功找到了三个最大且彼此不同的数字,就可以提前退出循环以节约时间        if (count_diff == 3) {            break;        }        // 当我们只录入了一个最大数,且当前遇到的数字不同于它时,它就是第二大数字        if (count_diff == 1 && ary[i] != big_one) {            count_diff++;            big_two = ary[i];        }        // 当我们已经录入了两个数值,且当前遇到的数字不同于它们时,它就是第三大数字        if (count_diff == 2 && ary[i] != big_two) {            count_diff++;            big_thr = ary[i];        }    }    // 根据收集到的不同的数字个数(范围 1 到 3),开始分情况结算    if (count_diff == 1) {        // 数据里只有一种相同的数字,取模总是 0,符合条件的唯一数字不足两个        std::cout << -1;    } else if (count_diff == 2) {        // 数据去重后只有两种数字,最大的取模情况是由小的数得到,即 big_two        // 而严格次大的取模结果只能是大数%小数去产生,即:最大数字取模次大数字        std::cout << big_one % big_two;    } else {        // 原数组含有超过三种以上的去重数字时:        // 比较【第三大数字】与【最大数字对第二大数字取余】,选取它们中间的较大者        std::cout << std::max(big_thr, big_one % big_two);    }    return 0;}

解法二:巧用 C++ 标准模板库(STL)中 std::unique 简化逻辑

为了更优雅地获得上述数组中“去重”的一系列不重复数字,我们可以直接利用 <algorithm> 头文件中强大的 std::unique 去重算法来帮我们轻松提取这段升序的不重复数字内容。

#include<iostream>#include<algorithm>// 全局数组存放数据int a[200005];intmain(){    int n;    std::cin >> n;    for (int i = 0; i < n; i++) {        std::cin >> a[i];    }    // 第 1 步:与上一版本相同,首先进行升序排序    std::sort(a, a + n);    // 第 2 步:std::unique 会通过前后对比将不重复的元素紧凑排在数组前面    // 并返回最后新生成无重复逻辑序列段尾部的下一位指针。    // 将该结果指针减去原数组首地址(a),正好能够直接得到【不同元素的个数 k】!    int k = std::unique(a, a + n) - a;    // 第 3 步:直接利用提取出的长度为 k 的唯一且升序序列,复用推导思想完成分类判断    if (k == 1) {        // 只有 1 个不同的元素,任何互相取余的结果一定都是 0,由于不足两项故返回 -1        std::cout << -1 << std::endl;    } else if (k == 2) {        // 只有 2 个不同的元素(索引互通为 0 和 1)        // 最大的数字一定可以被 a[0] % a[1] 产生。        // 所以次大必须是大数字除小数,即:        std::cout << a[1] % a[0] << std::endl;    } else {        // 至少有 3 个不同的元素,末端最大的三个互异数值对应位置肯定是下标的 [k-1], [k-2] 以及 [k-3]        // 按照之前的推导方法,这二者中的极大值代表了所有排列组合能产生的严格次大元素:        // 候选一:严格第三大数字,即 a[k-3]        // 候选二:最大数字 % 严格次大数字,即 a[k-1] % a[k-2]        std::cout << std::max(a[k - 3], a[k - 1] % a[k - 2]) << std::endl;    }    return 0;}

【推荐】【GESP】C++ 认证学习资源汇总(2026年3月更新)

【推荐】GESP/CSP/NOI资料站https://wiki.coderli.com/

【推荐】GESP/CSP学习交流群

【CSP】CSP-J 2019 江西真题 | 次大值 luogu-P5682 (适合GESP四、五级及以上考生练习) 第16张

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

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

抱歉,评论功能暂时关闭!