线段的最少分组

1 - 模型描述

在数轴上,有 \(n\) 条线段,对于第 \(i\) 条线段覆盖的区间为 \([l_i, r_i]\) ,现在你需要对这 \(n\) 条线段按如下的规则进行分组:

  • 同一组的线段之间互不重叠,即对于同一组的线段 \(i, j\) 存在 \(l_j > r_i\)\(r_j < l_i\).

求最少需要分多少组,并输出分组方案。

2 - 结论 & 证明 & 做法

结论:

最小分组等于某点上最大线段覆盖数量。

设最大线段覆盖数量为 \(M\).

证明:

  • 下界:任取一点 \(x\),覆盖 \(x\) 的所有区间两两重叠,必须被分到不同组。因此最少组数 \(\geq\) 覆盖 \(x\) 的线段数。取被覆盖最多的 \(x\),最少组数 \(\geq\) 最大覆盖数 \(M\).
  • 上界:将所有线段按左端点升序排列,维护每一个已建组的 “最大右端点” 集合。
    • 若当前线段的左端点 \(>\) 某一组的最大右端点,则把该线段加入这一组,并更新这一组的最大右端点。
    • 否则为该线段新开一组。
  • 当不得不新开一组时,说明所有已有组的最大右端点都 \(\geq\) 当前线段的左端点 \(l\). 于是:
    • 这些已有组各自的最靠右的线段都覆盖点 \(l\);
    • 加上当前线段也覆盖 \(l\);
    • 所以在点 \(l\) 处至少有(当前分组数 + 1)条线段同时覆盖,覆盖数 \(\geq\) 已建组数 + 1.
  • 推论:在分组过程的 “分组数量” 每增长 1,都能在某个点被覆盖数 “见证”。因此在过程中的任意时刻的分组数量 \(\leq\) 最大覆盖数量 \(M\).
  • 将上下界结合得到,最少组数 = 最大覆盖数量 \(M\).

做法:

  • 思路:按线段左端点 \(l_i\) 升序贪心。维护 “当前已建分组” 的最右端点。
  • 使用小根堆或有序集合维护当前线段是否需要新开一组,同时维护分组方案。
    • 小根堆:\(pair\) 存当前组的最右端点,组号。处理线段 \([l_i, r_i]\) 时:
      • 若堆顶的 \(r < l_i\) 则将此线段加入该组;
      • 否则新开一组。
    • 有序集合:\(pair\) 存当前组的最右端点,组号。每次 lower_bound({l_i, -1}) 找到第一个 \(r \geq l_i\) 的位置,若存在前驱则使用前驱所在组,否则新开一组。
  • 复杂度 \(O(n \cdot logn)\)

3 - 例题 - CSES1164

题目描述:

这里有一家酒店, 和 \(n\) 为即将到达的客人. 每一位客人都需要一间单独的房间。

你知道每位客人的到达日期和离开日期。两位客人只有在第一位到达的客人离开的时间早于第二位客人到达的时间时,才会被分配到同一间房间。

我们最少需要为所有客人总共准备多少房间? 以及如何分配房间?

输入格式:

第一行输入一个整数 \(n\) \((1 \le n \le 2 \cdot 10^5)\): 顾客的数量。

接下来的 \(n\) 行, 每行描述一位顾客。 每一行输入两个整数 \(a\)\(b\) \((1 \le a \le b \le 10^9)\): 到达日期与离开日期。

输出格式:

第一行输出一个整数 \(k\): 最小需要客房数量。

接下来的一行,依次输出为每一位顾客分配的房间号。房间号必须为 \(1,2,\ldots,k\). 你可以输出任意答案。

思路:

  • 将每一位客人的行程想象成线段,左端点为到达日期,右端点为离开日期。
  • 然后使用模型方法解决即可。

参考代码:

#include <bits/stdc++.h>
using namespace std;

#define endl '\n'
#define i64 long long

struct Customer{
    int arrive, leave, number;
};

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr), cout.tie(nullptr);

    int n;
    cin >> n;

    vector<Customer> cus(n);
    for (int i = 0; i < n; i ++ ) {
        cin >> cus[i].arrive >> cus[i].leave;
        cus[i].number = i;
    }

    ranges::sort(cus, [&](auto a, auto b) {
        return a.arrive < b.arrive;
    });

    int cnt = 0;
    vector<int> ans(n);
    set<pair<int, int>> leaveTime;

    for (int i = 0; i < n; i ++ ) {
        int arrive = cus[i].arrive;
        int leave = cus[i].leave;
        int number = cus[i].number;
        auto it = leaveTime.lower_bound({arrive, -1});
        if (it == leaveTime.begin()) {
            ans[number] = ++cnt;
            leaveTime.insert({leave, cnt});
        }
        else {
            --it;
            ans[number] = it->second;
            leaveTime.erase(it);
            leaveTime.insert({leave, ans[number]});
        }
    }

    cout << cnt << endl;
    for (int i = 0; i < n; i ++ ) {
        cout << ans[i] << " \n"[i == n - 1];
    }
}

4 - 练习

NC201994: https://ac.nowcoder.com/acm/problem/201994

posted @ 2025-12-10 20:59  算法蒟蒻沐小白  阅读(7)  评论(0)    收藏  举报