科普类资源清单![]() |
|---|
| 信奥业余科普 |

GESP学习资源清单

| 一级真题解析 | 一级练习题清单 | 111-8级全考纲解析 |
| 二级真题解析 | 二级练习题清单 | GESP/CSP必备技能 |
| 三级真题解析 | 三级练习题清单 | 考纲解密 |
| 四级真题解析 | 四级练习题清单 | 资源汇总/经验交流 |
| 五级真题解析 | 五级练习题清单 | |
| 六级真题解析 | 六级练习题清单 |

CSP学习资源清单

| 2025辽宁CSP-XL复赛真题解析 | CSP-J 编程题真题解析 |
| CSP-J 客观题真题解析 |

NOI学习资源清单

| 1998年真题解析 |
| 1999年真题解析 |
| 2001年真题解析 |
| 2011年真题解析 |
CSP-J 2021真题-插入排序,模拟与排序考点,重点考察对插入排序稳定性的理解以及增量更新排名的优化能力,适合GESP四-六级及以上考生练习,难度⭐⭐⭐,洛谷难度等级普及/提高−。
P7910 [CSP-J 2021] 插入排序
题目要求
题目描述
插入排序是一种非常常见且简单的排序算法。小 Z 是一名大一的新生,今天 H 老师刚刚在上课的时候讲了插入排序算法。
假设比较两个元素的时间为 ,则插入排序可以以 的时间复杂度完成长度为 的数组的排序。不妨假设这 个数字分别存储在 之中,则如下伪代码给出了插入排序算法的一种最简单的实现方式:
这下面是 C/C++ 的示范代码:
for (int i = 1; i <= n; i++)for (int j = i; j >= 2; j--)if (a[j] < a[j-1]) {int t = a[j-1]; a[j-1] = a[j]; a[j] = t; }这下面是 Pascal 的示范代码:
for i:=1 to n do for j:=i downto 2 do if a[j]<a[j-1] then begin t:=a[i]; a[i]:=a[j]; a[j]:=t; end;为了帮助小 Z 更好的理解插入排序,小 Z 的老师 H 老师留下了这么一道家庭作业:
H 老师给了一个长度为 的数组 ,数组下标从 开始,并且数组中的所有元素均为非负整数。小 Z 需要支持在数组 上的 次操作,操作共两种,参数分别如下:
:这是第一种操作,会将 的第 个元素,也就是 的值,修改为 。保证 ,。注意这种操作会改变数组的元素,修改得到的数组会被保留,也会影响后续的操作。
:这是第二种操作,假设 H 老师按照上面的伪代码对 数组进行排序,你需要告诉 H 老师原来 的第 个元素,也就是 ,在排序后的新数组所处的位置。保证 。注意这种操作不会改变数组的元素,排序后的数组不会被保留,也不会影响后续的操作。
H 老师不喜欢过多的修改,所以他保证类型 的操作次数不超过 。
小 Z 没有学过计算机竞赛,因此小 Z 并不会做这道题。他找到了你来帮助他解决这个问题。
输入格式
第一行,包含两个正整数 ,表示数组长度和操作次数。
第二行,包含 个空格分隔的非负整数,其中第 个非负整数表示 。
接下来 行,每行 个正整数,表示一次操作,操作格式见【题目描述】。
输出格式
对于每一次类型为 的询问,输出一行一个正整数表示答案。
输入输出样例 #1
输入 #1
3 43 2 12 31 3 22 22 3输出 #1
112说明/提示
【样例解释 #1】
在修改操作之前,假设 H 老师进行了一次插入排序,则原序列的三个元素在排序结束后所处的位置分别是 。
在修改操作之后,假设 H 老师进行了一次插入排序,则原序列的三个元素在排序结束后所处的位置分别是 。
注意虽然此时 ,但是我们不能将其视为相同的元素。
【样例 #2】
见附件中的 sort/sort2.in 与 sort/sort2.ans。
该测试点数据范围同测试点 。
【样例 #3】
见附件中的 sort/sort3.in 与 sort/sort3.ans。
该测试点数据范围同测试点 。
【样例 #4】
见附件中的 sort/sort4.in 与 sort/sort4.ans。
该测试点数据范围同测试点 。
【数据范围】
对于所有测试数据,满足 ,,,。
对于所有测试数据,保证在所有 次操作中,至多有 次操作属于类型一。
各测试点的附加限制及分值如下表所示。
附件:sort.zip(详见洛谷网站对应题目页)
题目分析
本题基于插入排序算法构造了一个带修改的排名查询问题。需要在理解插入排序行为的基础上,设计一套高效的排名预计算与增量更新机制。
1. 理解插入排序的稳定性
观察伪代码中的交换条件 if (a[j] < a[j-1]),只在严格小于时才交换,值相等时不交换。这是稳定排序的特征:值相同的元素保持原始下标的先后顺序。因此,排序后元素的位置完全由排序关键字 决定——先按值升序,值相同时按原始下标升序。
2. 将排名转化为计数
元素 排序后的位置等于"排在它前面的元素个数" 。元素 排在 前面,当且仅当 ,或 且 。公式表示为:
3. 预计算初始排名
读入数组后,对每个元素遍历整个数组统计排在它前面的元素数量,即可得到初始排名数组。时间复杂度 ,对于 完全可以接受。
4. 修改操作的增量更新
执行操作 1 x v 时,只有 的值发生了变化。对其他元素 的排名影响是局部的——只取决于 和 之间的相对排序关系是否因修改而改变。对于每个 ,比较修改前后 是否排在 前面:
如果 从"排在 前面"变为"不在前面": 减 。 如果 从"不在 前面"变为"排在前面": 加 。
最后单独重新计算 本身。每次修改操作时间复杂度 ,不能对所有元素都用 的完整重算方式(那样每次修改将变成 ,总计 ,会超时)。
5. 查询与总体复杂度
排名数组始终维护最新状态,查询操作只需 直接输出。总体时间复杂度:初始化 + 修改 + 查询 ,约为 ,完全在题目限制内。
示例代码
#include<iostream>intmain(){int n, Q;std::cin >> n >> Q;int a[8001]; // 原始数组,下标从 1 开始int rnk[8001]; // rnk[i] 表示 a[i] 在插入排序后的位置(排名)// 读入初始数组for (int i = 1; i <= n; i++) {std::cin >> a[i];}// 预计算初始排名// rnk[i] = 1 + 所有"排在 i 前面"的元素个数// 元素 j 排在 i 前面的条件:a[j] < a[i],或 a[j] == a[i] 且 j < ifor (int i = 1; i <= n; i++) {rnk[i] = 1;for (int j = 1; j <= n; j++) {if (j == i) {continue;}if (a[j] < a[i] || (a[j] == a[i] && j < i)) {rnk[i]++;}}}// 处理 Q 次操作while (Q--) {int op;std::cin >> op;if (op == 1) {// 修改操作:将 a[x] 改为 vint x, v;std::cin >> x >> v;int old_val = a[x]; // 记录修改前的旧值a[x] = v; // 执行修改// 增量更新其他元素的排名// 逐一检查每个元素 i 与 x 的相对顺序是否因修改而改变for (int i = 1; i <= n; i++) {if (i == x) {continue;}// 修改前,x 是否排在 i 前面?bool old_x_before_i = (old_val < a[i]) || (old_val == a[i] && x < i);// 修改后,x 是否排在 i 前面?bool new_x_before_i = (v < a[i]) || (v == a[i] && x < i);if (old_x_before_i && !new_x_before_i) {// x 原来排在 i 前面,现在不再排在前面// i 前面少了一个元素,排名前移 1 位rnk[i]--;} else if (!old_x_before_i && new_x_before_i) {// x 原来不排在 i 前面,现在排到了前面// i 前面多了一个元素,排名后移 1 位rnk[i]++;}}// 重新计算 x 自身的排名rnk[x] = 1;for (int j = 1; j <= n; j++) {if (j == x) {continue;}if (a[j] < a[x] || (a[j] == a[x] && j < x)) {rnk[x]++;}}} else {// 查询操作:输出 a[x] 排序后所处的位置int x;std::cin >> x;std::cout << rnk[x] << "\n";}}return 0;}
【推荐】:【GESP】C++ 认证学习资源汇总(2026年3月更新)
【推荐】:GESP/CSP/NOI资料站:https://wiki.coderli.com/
【推荐】GESP/CSP学习交流群

“luogu-”系列题目可在洛谷题库进行在线评测。
“bcqm-”系列题目可在编程启蒙题库进行在线评测。
科普类资源清单
11