题解:学而思编程 食堂排队

【题目来源】

学而思编程:食堂排队

【题目描述】

某班有 \(n\) 个学生,编号 \(1\sim n\)。中午下课后,学生们陆续赶到食堂吃饭。食堂只有一个打饭窗口,所以学生们要排队打饭。

\(i\) 个学生在第 \(l_i\) 分钟开始排队(当然是排在队尾)。如果同一分钟有多个学生同时开始排队,则编号较小的学生排在编号较大的学生之前。当一名学生排在队伍的最前端时,即轮到该名学生打饭。每个学生打饭需要花费一分钟时间。打到饭的学生,立即离开队伍,没打到饭的学生,在队伍里继续等待。

每个学生的耐心都是有限的。对于第 \(i\) 个学生,如果在第 \(r_i\) 分钟时,他仍在排队,且未轮到他打饭(仍有人排在他之前),那么他就会直接离开,放弃打饭。

对于每个学生,请你确定他是在第几分钟开始打饭(即排到队首)的?

【输入】

第一行包含整数 \(T\),表示共有 \(T\) 组测试数据。

每组数据第一行包含整数 \(n\)

接下来 \(n\) 行,每行包含两个整数 \(l_i,r_i\),表示第 \(i\) 个学生到达队伍的时间以及忍耐的极限时间。

保证同一组数据的 \(l_i\)非递减的。

【输出】

每组数据输出一行结果,包含 \(n\) 个整数,其中第 \(i\) 个整数表示第 \(i\) 个学生开始打饭的时间,如果这个学生直接离开,放弃打饭,则输出 \(0\)

【输入样例】

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

【输出样例】

1 2
1 0 2

【核心思想】

  1. 问题分析:给定 \(n\) 个学生,每个学生有到达时间 \(l_i\) 和忍耐极限 \(r_i\)。学生按到达时间排队(同分钟按编号),打饭需 \(1\) 分钟。若到 \(r_i\) 分钟仍未轮到自己则离开。求每个学生开始打饭的时间,或 \(0\)(离开)。这是一个贪心调度 + 区间覆盖问题,关键在于维护当前已分配的最大时间,为每个学生在其可用区间内分配最早的空闲时间。

  2. 算法选择

    • 贪心分配:维护当前已使用的最大时间 x,为每个学生分配满足条件的最小时间
    • 区间判断:每个学生的可用区间为 \([l_i, r_i]\),若 x 落在该区间内则分配 x,否则调整
  3. 关键步骤

    • 初始化:读取 \(T\)(测试组数)
    • 处理每组数据
      • 读取 \(n\)
      • x = 0:当前已分配的最大时间(初始为 \(0\),表示尚未开始)
      • 遍历 \(n\) 个学生:
        • 读取 \(l_i\)\(r_i\)
        • x < l_i
          • 输出 l_i:该学生在其到达时即可打饭
          • x = l_i + 1:更新已分配最大时间
        • 否则若 x > r_i
          • 输出 0:该学生忍耐时间已过,无法打饭
        • 否则(l_i <= x <= r_i):
          • 输出 x:该学生在当前最早可用时间打饭
          • x++:已分配最大时间推进 \(1\) 分钟
      • 输出换行
  4. 时间/空间复杂度

    • 时间复杂度:\(O(T \times n)\),每组数据线性处理
    • 空间复杂度:\(O(1)\),仅需常数变量
  5. 贪心调度的核心思想

    • 最早可用时间原则:为每个学生分配不早于其到达时间、不晚于其忍耐时间的最早空闲时间,保证后续学生有最大可用空间
    • 时间线维护x 维护当前时间线的最右端,表示下一个可用时间点。若 x < l_i 说明有空档,从 l_i 开始;若 x > r_i 说明该学生已错过;否则 x 恰好可用
    • 非递减到达的利用:题目保证 \(l_i\) 非递减,因此按顺序处理即可,无需排序或优先队列
    • 打饭时间的连续性:每个打饭操作占用 \(1\) 分钟,x 的递增模拟了时间的线性推进
    • 适用于"区间调度 + 贪心分配"类问题,维护当前时间线即可实现最优分配

【解题思路】

【算法标签】

模拟

【代码详解】

#include <bits/stdc++.h>
using namespace std;
#define int long long
int t, n, l, r, x;  // t: 测试用例数, n: 区间个数, l,r: 区间左右端点, x: 当前已使用的最大值
signed main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);  // 加速输入输出

    cin >> t;  // 输入测试用例数
    while (t--)  // 处理每个测试用例
    {
        cin >> n;  // 输入区间个数
        x = 0;  // 初始化当前已使用的最大值

        while (n--)  // 处理每个区间
        {
            cin >> l >> r;  // 输入区间[l, r]

            if (x < l)  // 如果当前最大值小于区间左端点
            {
                cout << l << " ";  // 输出左端点
                x = l + 1;  // 更新当前最大值
            }
            else if (x > r)  // 如果当前最大值大于区间右端点
            {
                cout << 0 << " ";  // 输出0,表示无法选择
            }
            else  // 当前最大值在区间内
            {
                cout << x << " ";  // 输出当前最大值
                x++;  // 当前最大值加1
            }
        }
        cout << endl;  // 换行
    }
    return 0;
}

【运行结果】

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