惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

酷 壳 – CoolShell
酷 壳 – CoolShell
H
Hacker News: Front Page
P
Palo Alto Networks Blog
T
ThreatConnect
Apple Machine Learning Research
Apple Machine Learning Research
博客园_首页
T
True Tiger Recordings
P
Privacy & Cybersecurity Law Blog
B
Blog
IT之家
IT之家
Last Week in AI
Last Week in AI
F
Full Disclosure
Hacker News: Ask HN
Hacker News: Ask HN
C
Comments on: Blog
Microsoft Azure Blog
Microsoft Azure Blog
C
Cybersecurity and Infrastructure Security Agency CISA
Microsoft Security Blog
Microsoft Security Blog
博客园 - 【当耐特】
N
News and Events Feed by Topic
NISL@THU
NISL@THU
腾讯CDC
雷峰网
雷峰网
Security Latest
Security Latest
李成银的技术随笔
M
Microsoft Research Blog - Microsoft Research
L
LangChain Blog
L
Lohrmann on Cybersecurity
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
C
Check Point Blog
Y
Y Combinator Blog
Recent Announcements
Recent Announcements
博客园 - Franky
N
News | PayPal Newsroom
V
V2EX
A
About on SuperTechFans
The Register - Security
The Register - Security
月光博客
月光博客
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
Google Online Security Blog
Google Online Security Blog
MyScale Blog
MyScale Blog
Cisco Talos Blog
Cisco Talos Blog
Vercel News
Vercel News
WordPress大学
WordPress大学
C
Cyber Attacks, Cyber Crime and Cyber Security
The Hacker News
The Hacker News
IntelliJ IDEA : IntelliJ IDEA – the Leading IDE for Professional Development in Java and Kotlin | The JetBrains Blog
IntelliJ IDEA : IntelliJ IDEA – the Leading IDE for Professional Development in Java and Kotlin | The JetBrains Blog
爱范儿
爱范儿
A
Arctic Wolf
L
LINUX DO - 最新话题
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More

博客园 - 小纸条

ruoyiai 启动指南 反向传播 numpy的使用 B 和 B+树 红黑树 ruoyi-vue 梯度下降法 博弈论 离散化 AcWing 907. 区间覆盖 AcWing 908 最大不相交区间数量 AcWing 905. 区间选点 AcWing 104. 货仓选址 动态规划经典题 窗口函数 1226. 哲学家进餐 1195. 交替打印字符串 1117. H2O 生成 1116. 打印零与奇偶数 关联子查询
AcWing 906. 区间分组
小纸条 · 2025-11-22 · via 博客园 - 小纸条

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));
    }
}