题解:洛谷 B4502 礼盒排序

【题目来源】

洛谷:B4502 [GESP202603 四级] 礼盒排序 - 洛谷

【题目描述】

商店推出了许多礼盒,每个礼盒中包含 \(k\) 件商品,每件商品都有一个价格。

现在需要对这些礼盒进行排序,排序规则如下:

  1. 先按礼盒总价格从小到大排序;
  2. 如果总价格相同,按礼盒中最贵商品的价格从小到大排序;
  3. 如果仍然相同,按礼盒中最便宜商品的价格从小到大排序;
  4. 如果仍然相同,按礼盒编号从小到大排序。

请输出排序后的礼盒编号。

【输入】

第一行包含两个整数 \(n\)\(k\),分别表示礼盒数量和每个礼盒中商品的数量。

接下来 \(n\) 行,每行包含 \(k\) 个整数,第 \(i\) 行表示第 \(i\) 个礼盒中各商品的价格。

【输出】

输出一行,包含排序后的礼盒编号(编号从 \(1\) 开始),用空格分隔。

【输入样例】

4 3
3 5 2
4 1 5
2 2 4
3 4 3

【输出样例】

3 4 2 1

【核心思想】

  1. 问题分析:给定 \(n\) 个礼盒,每个礼盒包含 \(k\) 件商品的价格。需要按四重优先级排序:礼盒总价格升序 → 最贵商品价格升序 → 最便宜商品价格升序 → 礼盒编号升序。这是一个多关键字自定义排序问题,核心在于正确提取每个礼盒的统计特征并按优先级比较。

  2. 算法选择

    • 结构体封装:将礼盒的编号、总价格、最大价格、最小价格封装为结构体
    • 自定义比较器:按四重优先级定义比较函数,配合排序算法完成排序
  3. 关键步骤

    • 读入数据:读取 \(n, k\)
    • 预处理每个礼盒\(i\)\(1\)\(n\)):
      • 读入 \(k\) 个价格,同时计算:
        • \(tot \leftarrow\) 所有价格之和
        • \(maxn \leftarrow\) 最大价格
        • \(minn \leftarrow\) 最小价格
      • 存储结构体:\(a[i] = \{i, maxn, minn, tot\}\)
    • 自定义排序(比较函数 \(cmp\)):
      • \(x.tot \neq y.tot\):返回 \(x.tot < y.tot\)
      • 否则若 \(x.maxn \neq y.maxn\):返回 \(x.maxn < y.maxn\)
      • 否则若 \(x.minn \neq y.minn\):返回 \(x.minn < y.minn\)
      • 否则:返回 \(x.id < y.id\)
    • 输出结果:按排序后的顺序输出 \(a[i].id\)
  4. 时间/空间复杂度

    • 时间复杂度:\(O(n \cdot k + n \log n)\),预处理每个礼盒为 \(O(k)\),排序为 \(O(n \log n)\)
    • 空间复杂度:\(O(n)\),存储 \(n\) 个结构体
  5. 多关键字排序的核心思想

    • 统计特征提取:在读入时同步计算总价格、最大价格、最小价格,避免二次遍历,将 \(O(n \cdot k)\) 的预处理与读入合并
    • 优先级链式比较:比较函数按优先级从高到低依次判断,高优先级不同时直接返回结果,高优先级相同时才比较下一级,确保排序结果符合题意
    • 稳定排序非必需:由于第四优先级是唯一的礼盒编号,保证不会出现完全相同的比较结果,因此无需稳定排序
    • 结构体绑定策略:将编号与计算得到的统计量绑定在一起,排序后仍能追溯原始身份,避免排序后丢失对应关系
    • 适用于多维度排序、需要按复合条件排列的自定义排序类问题

【算法标签】

普及- #其他排序

【代码详解】

#include <bits/stdc++.h>  // 包含所有标准库头文件
using namespace std;       // 使用标准命名空间

const int N = 1005;  // 定义常量N,表示最大学生数量

int n, k;  // n: 学生数量, k: 每个学生的成绩数量

// 定义学生结构体
struct Node
{
    int id;    // 学生编号
    int maxn;  // 该学生的最高成绩
    int minn;  // 该学生的最低成绩
    int tot;   // 该学生的总成绩
} a[N];  // 声明结构体数组a,用于存储所有学生的信息

// 自定义比较函数,用于排序
bool cmp(Node x, Node y)
{
    // 第一优先级:按总成绩从小到大排序
    if (x.tot != y.tot)
    {
        return x.tot < y.tot;
    }
    // 第二优先级:总成绩相同,按最高成绩从小到大排序
    else if (x.maxn != y.maxn)
    {
        return x.maxn < y.maxn;
    }
    // 第三优先级:总成绩和最高成绩都相同,按最低成绩从小到大排序
    else if (x.minn != y.minn)
    {
        return x.minn < y.minn;
    }
    // 第四优先级:所有成绩都相同,按学号从小到大排序
    return x.id < y.id;
}

int main()  // 主函数入口
{
    cin >> n >> k;  // 输入学生数量n和每个学生的成绩数量k

    // 读取每个学生的k个成绩
    for (int i = 1; i <= n; i++)
    {
        int x;  // 临时变量,用于读取每个成绩
        cin >> x;  // 读取第一个成绩

        int mx, mn, sum = 0;  // mx: 最高成绩, mn: 最低成绩, sum: 总成绩
        mx = mn = x;  // 初始化最高成绩和最低成绩为第一个成绩
        sum += x;     // 将第一个成绩加入总成绩

        // 读取剩余的k-1个成绩
        for (int j = 2; j <= k; j++)
        {
            cin >> x;        // 读取下一个成绩
            sum += x;        // 将成绩加入总成绩

            // 更新最高成绩
            if (x > mx)
            {
                mx = x;
            }

            // 更新最低成绩
            if (x < mn)
            {
                mn = x;
            }
        }

        // 将计算得到的值存入结构体数组
        a[i].id = i;      // 学号(从1开始的序号)
        a[i].maxn = mx;   // 最高成绩
        a[i].minn = mn;   // 最低成绩
        a[i].tot = sum;   // 总成绩
    }

    // 使用自定义比较函数对数组进行排序
    // 注意:数组下标从1开始,所以排序范围是a+1到a+n+1
    sort(a + 1, a + n + 1, cmp);

    // 输出排序后的学号
    for (int i = 1; i <= n; i++)
    {
        cout << a[i].id << " ";
    }
    cout << endl;  // 输出换行

    return 0;  // 程序正常结束
}

【运行结果】

4 3
3 5 2
4 1 5
2 2 4
3 4 3
3 4 2 1
posted @ 2026-07-16 14:58  团爸讲算法  阅读(33)  评论(0)    收藏  举报