洛谷 B4502 [GESP202603 四级] 礼盒排序——多关键字排序与排名计数法

洛谷 B4502 GESP202603 四级 礼盒排序------多关键字排序与排名计数法

📌 摘要

B4502 给定 n 个礼盒,每个含 k 件商品,按四级关键字排序:总价升序 → 最贵商品升序 → 最便宜商品升序 → 编号升序。这本质是多关键字字典序排序 (Lexicographic Multi-Key Sort)。原始代码没有用 sort + 自定义比较函数,而是用了排名计数法 :对每个礼盒,数有几个礼盒比它"更优",用这个计数作为最终位置。这种 O(n²) 的做法在 n≤10³ 时可行,且思路直白------不做交换,只数排名。本文从伪代码出发,延伸到数据库的 ORDER BY 多列排序、SQL 引擎的排序策略,以及排名计数法与计数排序(Counting Sort)的数学关系。

题目链接B4502 礼盒排序

📚 目录

  • [📝 前言](#📝 前言)

  • [🔍 题目在考什么](#🔍 题目在考什么)

  • [💡 思路](#💡 思路)

  • [📝 伪代码](#📝 伪代码)

  • [🎯 关键点](#🎯 关键点)

  • [⚠️ 注意事项](#⚠️ 注意事项)

  • [🌳 延伸:从礼盒到数据库------多关键字排序的工业实践](#🌳 延伸:从礼盒到数据库——多关键字排序的工业实践)

    • [🔤 字典序:不止用于字符串](#🔤 字典序:不止用于字符串)

    • [🗄️ ORDER BY:数据库的多关键字排序](#🗄️ ORDER BY:数据库的多关键字排序)

    • [🏆 排名计数法:用计数代替交换](#🏆 排名计数法:用计数代替交换)

    • [📊 计数排序:从排名到线性时间](#📊 计数排序:从排名到线性时间)

    • [🛒 电商排序:你每天在用的多关键字排序](#🛒 电商排序:你每天在用的多关键字排序)

  • [📚 延伸阅读文献](#📚 延伸阅读文献)


📝 前言

这篇题解没有源代码,只有伪代码。

作为一名信奥教练,我不提倡复制粘贴。我见过太多学生搜到题解、复制、粘贴、提交、AC------代码跑通了,脑子没跑通。下次遇到变体题,还是不会。

伪代码剥掉了语言的壳,只留算法的骨架。你看不到 #include,看不到 cincout,看不到那些让你以为"我会了"的语法细节。你能看到的只有:这一步做什么、下一步做什么、为什么这么做。

如果你是路过的友友,已经在这道题上挣扎了很久------先去喝杯水,回来重新看看自己卡在哪一步。是没读懂题意?是思路方向偏了?还是代码有 bug 但逻辑其实对?大多数时候不是不会,是走偏了。偏了不可怕,可怕的是偏了之后直接放弃,去抄一份能 AC 的代码。抄完你以为你懂了,其实你只是搬了别人的结论。

除非你时间真的紧张------比赛临近、作业要交------那种情况先 AC 再说,能理解。但平时练习,给自己一点耐心。先自己想、自己写、自己调,跑不过了再来看伪代码:你的思路和这里差在哪一步。那一步,就是你真正学到的东西。


🔍 题目在考什么

n 个礼盒,每个含 k 件商品。按四个关键字排序:

优先级 关键字 方向
1 礼盒总价(k 件商品价格之和) 升序
2 最贵商品价格 升序
3 最便宜商品价格 升序
4 礼盒编号 升序

这是多关键字字典序排序:先比第一关键字,平了比第二,再平比第三,最后比第四。

数据范围 n≤10³,k≤10,O(n²) 的排序算法完全可行。


💡 思路

原始代码用了排名计数法------不做交换,只数排名:

  1. 预处理 :对每个礼盒 i,计算总价、最大值、最小值,存入 temp_total[i] = [编号, 总价, 最大值, 最小值]
  2. 排名计算:对每个礼盒 i,初始排名 = n(最末)。遍历所有其他礼盒 j,如果 j 比 i "更优"(按四级关键字比较),排名 -1
  3. 放置sort_num[排名] = 礼盒编号

这和交换排序(冒泡、选择)不同------它不移动元素,只统计"有几个比我更优",用这个计数直接定位。

📝 伪代码

text 复制代码
读取 n, k
读取礼盒数组 gifts[0..n-1][0..k-1]

// 第一步:预处理------算总价、最大值、最小值
对 i = 0 到 n-1:
    编号[i] = i + 1
    总价[i] = gifts[i][0..k-1] 之和
    最大值[i] = gifts[i][0..k-1] 中的最大
    最小值[i] = gifts[i][0..k-1] 中的最小

// 第二步:排名计数法
对 i = 0 到 n-1:
    排名 = n                         // 初始假设排最后
    对 j = 0 到 n-1:                 // 和每个其他礼盒比较
        如果 j 比 i 更优:            // 按四级关键字比较
            排名--
    sort_num[排名] = 编号[i]         // 放到对应位置

// 第三步:输出
对 index = 0 到 n-1:
    输出 sort_num[index] + 空格

"j 比 i 更优"的比较逻辑(四级关键字字典序):

text 复制代码
函数 更优(j, i):
    如果 总价[j] < 总价[i]: 返回 true       // 第一关键字:总价更小
    如果 总价[j] == 总价[i]:
        如果 最大值[j] < 最大值[i]: 返回 true  // 第二关键字:最大值更小
        如果 最大值[j] == 最大值[i]:
            如果 最小值[j] < 最小值[i]: 返回 true  // 第三关键字:最小值更小
            如果 最小值[j] == 最小值[i]:
                如果 编号[j] < 编号[i]: 返回 true    // 第四关键字:编号更小
    返回 false

🎯 关键点

多关键字比较 = 字典序。 两个礼盒比较时,先比总价,总价相同比最大值,最大值相同比最小值,最小值相同比编号。这和字符串字典序比较一模一样------先比第一个字符,相同比第二个......只是"字符"变成了"关键字"。

排名计数法的正确性。 对礼盒 i,如果有 m 个礼盒比它更优,那它的排名是第 m+1(0 基下标为 m)。因为所有关键字都能确定严格全序(最后有编号兜底),不会有两个礼盒排名相同。原始代码用"初始排名 = n,每遇到一个更优的 -1"实现这个逻辑。

"编号兜底"保证全序。 如果两个礼盒总价、最大值、最小值全部相同,最后比编号。编号互不相同,所以任意两个礼盒都能分出先后------不存在"并列"。

用样例追踪(n=4, k=3):

礼盒 商品价格 总价 最大值 最小值
1 3 5 2 10 5 2
2 4 1 5 10 5 1
3 2 2 4 8 4 2
4 3 4 3 10 4 3

排名计算:

礼盒 比它更优的有 排名(0基) sort_num排名
1 2,3,4(3号总价更小;4号总价同但最大值更小;2号总价同、最大值同但最小值更小) 3 1
2 3,4(3号总价更小;4号总价同但最大值更小;1号?总价同、最大值同、最小值2>1,所以1不比2优) 2 2
3 无(总价最小) 0 3
4 3(3号总价更小) 1 4

输出:3 4 2 1。✓

为什么不用 sort 原始代码选择手写排名计数而不是 sort + 自定义比较函数,可能是因为多关键字比较逻辑复杂,写成 cmp 函数容易出错。排名计数法虽然 O(n²),但逻辑直白:数有几个比你强,你就是第几名。对学生来说,这种"排名"思维比"交换"思维更直觉。


⚠️ 注意事项

  • VLA 问题。 int gifts[n][k]int temp_total[n][4] 是变长数组,不是标准 C++。可用 vector 或固定大小数组替代。

  • 排名计数法的复杂度。 O(n²),n=10³ 时是 10⁶ 次比较,1 秒内没问题。但 n=10⁵ 时会超时------这时应该用 sort + 比较函数 O(n log n)。

  • 比较逻辑的完整性。 四级关键字必须完整实现,漏掉任何一级都会导致排序错误。特别是第四级"编号"------如果三级都相同但没比编号,两个礼盒会获得相同排名,sort_num 会覆盖。

  • "自比"处理。 原始代码中 i==j 时需要特殊处理(不能自己比自己更优)。代码用 if(i==j) temp_sort_index-- 处理------初始排名是 n(最末),自比时 -1 变成 n-1,然后再被其他更优的 -1。需要仔细理解这个逻辑。

  • 输出格式。 编号之间用空格分隔,末尾也有空格。漏空格或多余换行会格式错误。


🌳 延伸:从礼盒到数据库------多关键字排序的工业实践

你在 B4502 里做的多关键字排序,在工业界有一个更常见的名字------ORDER BY

🔤 字典序:不止用于字符串

字典序(Lexicographic Order)不局限于字符串比较------任何"有序的多个属性"都可以用字典序比较(Lexicographic Order --- Wikipedia)

场景 第一关键字 第二关键字 第三关键字
B4502 礼盒 总价 最大值 最小值
字典排序 首字母 第二字母 第三字母
学生排名 总分 数学 语文
航班排序 价格 起飞时间 航程时长
商品搜索 相关度 销量 好评率

核心规则一样:先比第一关键字,平了比第二,再平比第三...... 你在 B4502 里写的 if 总价相同 → if 最大值相同 → if 最小值相同 → 比编号,和字典里查单词 appleapplication 前面(第三个字母 p < l)做的是同一件事。

🗄️ ORDER BY:数据库的多关键字排序

SQL 的 ORDER BY 就是多关键字排序的直接实现(SQL ORDER BY --- W3Schools)

你的 B4502 等价于这条 SQL:

sql 复制代码
SELECT box_id
FROM gifts
ORDER BY total_price ASC, max_price ASC, min_price ASC, box_id ASC;
B4502 代码 SQL 等价
temp_total[i][1](总价) total_price
temp_total[i][2](最大值) max_price
temp_total[i][3](最小值) min_price
temp_total[i][0](编号) box_id
四级 if-else 嵌套 ORDER BY ... ASC, ... ASC, ... ASC, ... ASC

数据库引擎在执行 ORDER BY 时,内部用什么算法?取决于数据量(Database Sorting --- VLDB)

数据量 数据库用的排序 对应你学过的
小(内存放得下) 快排 / 内部归并排序 sort()
大(内存放不下) 外部归并排序 归并排序的磁盘版
有索引 索引扫描(已排序) 不需要排序

你的排名计数法 O(n²) 在工业界不会用------太慢。但它揭示了一个重要的概念:排序的本质是确定每个元素的"位置",而位置 = 比它更优的元素个数

🏆 排名计数法:用计数代替交换

你的代码做了一件有趣的事------不交换任何元素,只数"有几个比我更优",用这个计数直接定位(Comparison Sort --- Wikipedia)

排序方法 核心操作 复杂度 思维模式
冒泡排序 相邻比较 + 交换 O(n²) "你比我大,换位置"
选择排序 找最小 + 交换 O(n²) "找出最小的,放最前"
排名计数法 数"有几个更优" O(n²) "数出排名,直接放"
快速排序 分治 + 递归 O(n log n) "比基准小的去左边"

排名计数法的优势是不需要交换 ------适合"元素很重(复制成本高)"的场景。比如排序大型结构体时,排名计数法只需要记一个 int 排名,最后一次性放到正确位置。缺点是 O(n²),大数据量不能用。

📊 计数排序:从排名到线性时间

排名计数法和计数排序 (Counting Sort)名字像但原理不同(Counting Sort --- Wikipedia)

你的排名计数法 计数排序
数什么 数"有几个元素比我更优" 数"值等于 v 的有几个"
适用范围 任意可比较的元素 整数,值域有限
复杂度 O(n²) O(n + k)
稳定性 稳定(编号兜底) 稳定

但两者共享一个核心思想:不比较+交换,而是用计数确定位置。这是非比较排序的精髓------如果你能通过计数知道每个元素的最终位置,就不需要交换。

🛒 电商排序:你每天在用的多关键字排序

淘宝、京东的商品搜索结果排序,就是 B4502 的工业版(E-commerce Sorting --- Wikipedia)

B4502 淘宝搜索
总价升序 价格升序(或综合评分降序)
最大值升序 销量降序
最小值升序 好评率降序
编号升序 商品ID升序(兜底)

当你在淘宝搜索"手机壳",选择"价格从低到高"时,后台执行的 SQL 就是:

sql 复制代码
SELECT product_id
FROM products
WHERE category = '手机壳'
ORDER BY price ASC, sales DESC, rating DESC, product_id ASC;

这条 SQL 和你 B4502 的代码做的是完全一样的事------多关键字字典序排序。区别只是关键字不同、数据量大了几百万倍、底层排序算法从 O(n²) 变成了 O(n log n)。


📚 延伸阅读文献

论文与技术文档
  1. D. E. Knuth. The Art of Computer Programming, Vol. 3: Sorting and Searching (2nd Edition). Addison-Wesley, 1998. ------ 排序算法的圣经,含多关键字排序和计数排序。
  2. H. A. H. van der Velden. Sorting and Ranking in Information Retrieval. Information Processing & Management, 1975. ------ 信息检索中的排序与排名。
在线资源
  1. 洛谷. B4502 GESP202603 四级 礼盒排序 . https://www.luogu.com.cn/problem/B4502
  2. SQL ORDER BY Tutorial --- W3Schools . https://www.w3schools.com/sql/sql_orderby.asp ------ SQL 多列排序教程。
  3. Lexicographic Order --- Wikipedia . (Lexicographic Order) ------ 字典序的数学定义和扩展。
  4. Comparison Sort --- Wikipedia . (Comparison Sort) ------ 比较排序的下界证明(O(n log n))。
  5. Counting Sort --- Wikipedia . (Counting Sort) ------ 计数排序原理,和排名计数法的对比。
  6. Sort-Merge Join --- Wikipedia . (Sort-Merge Join) ------ 数据库外部排序策略。
  7. Recommender System --- Wikipedia . (Recommender System) ------ 推荐系统的多关键字排序。
推荐教材
  • T. H. Cormen, C. E. Leiserson, R. L. Rivest, C. Stein. Introduction to Algorithms (4th Edition). MIT Press, 2022. ------ 第 5 章排序,第 8 章线性时间排序(计数排序)。

  • H. Garcia-Molina, J. Ullman, J. Widom. Database Systems: The Complete Book (2nd Edition). Pearson, 2008. ------ 含 ORDER BY 的实现和外部排序。

  • R. Baeza-Yates, B. Ribeiro-Neto. Modern Information Retrieval (2nd Edition). Addison-Wesley, 2011. ------ 搜索引擎中的多关键字排序。


本文标签:#算法 #多关键字排序 #字典序 #排名计数法 #计数排序 #ORDER BY #数据库 #电商 #洛谷题解 #信奥 #GESP #C++ #四级

本文首发于 CSDN,作者:HugoStudio_SWAN

相关推荐
计科杨某人1 小时前
简单算法题(基础入门题)
c++·算法·题解·入门·基础算法
Q一件事1 小时前
RWEQ计算——Wf因子
算法
zander2581 小时前
LeetCode 64. 最小路径和
算法·深度优先
find1star10 小时前
LeetCode 141:环形链表
java·算法·leetcode·链表
迅翼SwiftWing10 小时前
编队系列(总)|固定翼编队如何实现稳定飞行?通信架构、协同算法与工程约束深度解析
算法·架构
LB211210 小时前
力扣160 21 86
算法·leetcode·职场和发展
小智老师PMP11 小时前
2026深度解析|PMP第八版与NPDP核心侧重点本质区别(管理类证书怎么选)
开发语言·分布式·算法·职场和发展·产品经理
HugoStudio_SWAN11 小时前
洛谷 B4450 / B3867 / B3923 智慧购物、储蓄与做题——从程序到生活
c++·学习·程序人生·算法·生活
a1879272183112 小时前
【算法】动态规划第二篇:双序列 DP 三课——继承、计步与断链
算法·leetcode·动态规划·dp·回溯·暴力·算法讲解