由于水平原因,博客大部分内容摘抄于网络,如有错误或者侵权请指出,本人将尽快修改

AcWing 906. 区间分组

AcWing 906. 区间分组

一、题目描述

给定 ( N ) 个闭区间 ([a_i, b_i]),将这些区间分成若干组,要求每组内部的区间两两之间(包括端点)没有交集,并使得组数尽可能小。输出最小组数。

输入格式

第一行包含整数 ( N ),表示区间数。
接下来 ( N ) 行,每行包含两个整数 ( a_i, b_i ),表示一个区间的两个端点。

输出格式

输出一个整数,表示最小组数。

数据范围

( 1 \leq N \leq 10^5 ),( -10^9 \leq a_i \leq b_i \leq 10^9 )

输入样例

3
-1 1
2 4
3 5

输出样例

2

二、题目解读

核心要求:组内区间无交集(含端点),组数最少。
可以类比为「活动安排问题」:有若干活动,每个活动有开始和结束时间,同一教室不能安排有交集的活动,求最少需要的教室数量(教室数即组数)。

三、小根堆贪心(按左端点排序)

核心思路

  1. 按左端点排序:将所有区间按左端点升序排列,确保按顺序处理区间时,左端点是递增的。
  2. 小根堆维护组的右端点:堆中存储每个组的「最右端点」,堆顶是所有组中右端点最小的组(优先尝试将当前区间放入右端点最小的组,尽可能留出空间给后续区间)。
  3. 分组逻辑
    • 若当前区间的左端点 > 堆顶(最小右端点),说明该区间与该组无交集,可放入该组,更新堆顶为当前区间的右端点。
    • 若当前区间的左端点 ≤ 堆顶,说明该区间与所有组都有交集,需新开一组,将当前区间的右端点加入堆。
  4. 堆的大小即为最小组数:堆中每个元素对应一个组,最终堆的大小就是所需的最小组数。

代码实现

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

const int N = 100010;

int n;
struct Node {
    int l, r;
    // 按左端点升序排序
    const bool operator<(const Node &b) const {
        return l < b.l;
    }
} range[N];

int main() {
    cin >> n;
    for (int i = 0; i < n; i++) cin >> range[i].l >> range[i].r;

    sort(range, range + n);

    // 小根堆:存储各组的右端点,堆顶为最小右端点
    priority_queue<int, vector<int>, greater<int>> heap;

    for (int i = 0; i < n; i++) {
        auto t = range[i];
        // 堆为空或当前区间与所有组有交集,新开一组
        if (heap.empty() || heap.top() >= t.l) {
            heap.push(t.r);
        } else {
            // 可放入右端点最小的组,更新该组右端点
            heap.pop();
            heap.push(t.r);
        }
    }

    cout << heap.size() << endl;
    return 0;
}

四、关键疑问:为什么不能按右端点排序?

按右端点排序会导致分组结果错误,举反例说明:
假设有 4 个区间:([1,5])、([2,3])、([4,6])、([7,8])。

  • 按右端点排序后:([2,3])、([1,5])、([4,6])、([7,8])。
  • 分组过程:
    1. 放入 ([2,3]),堆:{3}。
    2. ([1,5]) 的左端点 1 ≤ 3,新开一组,堆:{3,5}。
    3. ([4,6]) 的左端点 4 ≤ 3?否;4 ≤ 5?是,新开一组,堆:{3,5,6}。
    4. ([7,8]) 的左端点 7 > 3,替换堆顶,堆:{5,6,8}。
  • 结果:3 组。
  • 正确分组:([2,3]) 和 ([4,6]) 一组,([1,5]) 和 ([7,8]) 一组,仅需 2 组。

原因:按右端点排序会破坏区间左端点的递增性,导致无法合理利用组内空间,进而多开不必要的组。而按左端点排序能保证后续区间的左端点不小于之前的区间,确保贪心策略的有效性。

类似题目: https://www.luogu.com.cn/problem/U422228

import java.util.Arrays;
import java.util.Comparator;
import java.util.PriorityQueue;
import java.util.Scanner;

public class Main {
    static class Interval{
        private int l;
        private int r;

        public Interval(int l, int r) {
            this.l = l;
            this.r = r;
        }
    }
    public static int minMeetingRooms(Interval[] intervals) {
        if (intervals == null || intervals.length == 0) {
            return 0;
        }

        // 按会议开始时间升序排序
        Arrays.sort(intervals, Comparator.comparingInt(o -> o.l));

        // 最小堆:维护当前各会议室的“最早结束时间”
        PriorityQueue<Integer> minHeap = new PriorityQueue<>();

        // 第一个会议占用一个会议室
        minHeap.offer(intervals[0].r);

        for (int i = 1; i < intervals.length; i++) {
            int start = intervals[i].l;
            int end = intervals[i].r;

            // 如果当前会议开始时,最早结束的会议室已空闲(注意:>= 表示可复用)
            if (start >= minHeap.peek()) {
                minHeap.poll(); // 释放该会议室
            }

            // 当前会议占用一个会议室(无论是复用还是新开)
            minHeap.offer(end);
        }

        // 堆的大小 = 同时进行的最大会议数 = 最少会议室数
        return minHeap.size();
    }
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int n = sc.nextInt();
        Interval[] intervals = new Interval[n];
        for (int i = 0; i < n; i++) {
            int a = sc.nextInt();
            int b = sc.nextInt();
            intervals[i] = new Interval(a, b);
        }
        System.out.println(minMeetingRooms(intervals));
    }
}
posted @ 2025-11-22 23:42  小纸条  阅读(7)  评论(0)    收藏  举报