题解:洛谷 P1223 排队接水

【题目来源】

洛谷:P1223 排队接水 - 洛谷(luogu.com.cn)

【题目描述】

\(n\) 个人在一个水龙头前排队接水,假如每个人接水的时间为 \(T_i\),请编程找出这 \(n\) 个人排队的一种顺序,使得 \(n\) 个人的平均等待时间最小。

【输入】

第一行为一个整数 \(n\)

第二行 \(n\) 个整数,第 \(i\) 个整数 \(T_i\) 表示第 \(i\) 个人的等待时间 \(T_i\)

【输出】

输出文件有两行,第一行为一种平均时间最短的排队顺序;第二行为这种排列方案下的平均等待时间(输出结果精确到小数点后两位)。

【输入样例】

10
56 12 1 99 1000 234 33 55 99 812

【输出样例】

3 2 7 8 1 4 9 6 10 5
291.90

【核心思想】

  1. 问题分析:有 \(n\) 个人排队接水,每个人接水所需时间 \(T_i\) 不同。若某个人在第 \(i\) 位接水,则他之后的所有人(共 \(n-i\) 人)都会等待他完成,产生额外的等待时间。总等待时间等于每个人接水时间乘以其后面人数的总和。目标是找到一个排队顺序,使总等待时间(或平均等待时间)最小。

  2. 算法选择

    • 贪心策略(最短作业优先):将接水时间短的人排在前面。对于任意相邻两人,若前者时间大于后者,交换两者可使总等待时间减少,因为前者时间对后续的贡献更大。因此最优顺序为按接水时间从小到大排序。
    • 排序:使用结构体存储每个人的时间和原始编号,按时间升序排序。
    • 计算总等待时间:排序后,第 \(i\) 个人的时间 \(T_i\) 会对后面 \(n-i\) 个人产生等待,总等待时间 \(sum = \sum_{i=1}^{n} T_i \times (n-i)\),平均等待时间 \(= sum / n\)
  3. 关键步骤

    • 读入 \(n\) 和每个 \(T_i\),记录原始编号(因为输出需要按顺序输出人的编号)。
    • \(T_i\) 升序排序(若时间相同,编号顺序无关,但通常按输入顺序或任意)。
    • 输出排序后的编号序列,作为最优排队顺序。
    • 计算总等待时间 \(sum = \sum_{i=1}^{n} T_i \times (n-i)\)
    • 输出平均等待时间 \(sum / n\),保留两位小数。
  4. 时间/空间复杂度

    • 时间复杂度:\(O(n \log n)\),主要来自排序。
    • 空间复杂度:\(O(n)\),存储结构体数组。
  5. 贪心策略的核心思想

    • 交换论证:若存在相邻两人 \(i\) 在前、\(j\) 在后,且 \(T_i > T_j\),交换它们后,其他所有人的等待时间不变,而 \(i\)\(j\) 的等待时间变化量为 \(T_i - T_j\)(原来 \(i\) 影响 \(j\),交换后 \(j\) 影响 \(i\)),总等待时间减少 \(T_i - T_j > 0\)。因此,任何逆序都会降低总时间,故最优顺序为升序。
    • 局部最优到全局最优:每次选择当前剩余人中时间最短的放在最前面,该贪心选择不会使后续最优解变差。
    • 平均等待时间:总等待时间除以人数即为平均等待时间,两者同序优化。
    • 适用于单服务器、作业处理时间固定的调度问题,是经典的“最短处理时间优先”调度策略。

【解题思路】

【算法标签】

普及- #贪心

【代码详解】

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

// 定义人员结构体
struct people
{
    int time;  // 该人员完成任务所需时间
    int num;   // 人员编号
} ps[1005];    // 最多1005人

// 自定义排序比较函数:按完成任务时间升序排序
bool cmp(people p1, people p2)
{
    return p1.time < p2.time;
}

int main()
{
    int n;            // 总人数
    double sum = 0;  // 总等待时间
    double avg;       // 平均等待时间

    // 输入总人数
    cin >> n;

    // 输入每个人的任务完成时间,并记录人员编号
    for (int i = 1; i <= n; i++)
	{
        cin >> ps[i].time;
        ps[i].num = i;
    }

    // 按任务完成时间从短到长排序
    sort(ps + 1, ps + n + 1, cmp);

    // 输出最优排队顺序
    for (int i = 1; i <= n; i++)
	{
        cout << ps[i].num << " ";
        // 计算该人员后面所有人的等待时间
        sum += (n - i) * ps[i].time;
    }
    cout << endl;

    // 计算并输出平均等待时间(保留两位小数)
    avg = sum / n;
    printf("%.2f", avg);

    return 0;
}
#include <bits/stdc++.h>
using namespace std;
struct Node
{
    int t, id;  // 接水时间,编号
    bool operator < (Node &b)  // 按接水时间从小到大排序
    {
        return t < b.t;
    }
} a[1010];
int main()
{
    int n;
    cin >> n;  // 输入人数
    for (int i = 1; i <= n; i++)  // 输入每个人的接水时间
    {
        cin >> a[i].t;
        a[i].id = i;  // 记录原始编号
    }
    sort(a + 1, a + n + 1);  // 按接水时间排序
    for (int i = 1; i <= n; i++)  // 输出最优顺序
    {
        cout << a[i].id << " ";
    }
    cout << endl;
    double tim = 0;  // 总等待时间
    for (int i = 1; i <= n; i++)  // 计算平均等待时间
    {
        tim += a[i].t * (n - i);  // 第i个人的等待时间会影响后面n-i个人
    }
    printf("%.2lf", tim / n);  // 输出平均等待时间
    return 0;
}

【运行结果】

10
56 12 1 99 1000 234 33 55 99 812
3 2 7 8 1 4 9 6 10 5
291.90
posted @ 2026-09-02 22:51  团爸讲算法  阅读(21)  评论(0)    收藏  举报