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

推荐订阅源

The GitHub Blog
The GitHub Blog
Google Online Security Blog
Google Online Security Blog
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
酷 壳 – CoolShell
酷 壳 – CoolShell
Y
Y Combinator Blog
L
LangChain Blog
博客园_首页
云风的 BLOG
云风的 BLOG
博客园 - 叶小钗
月光博客
月光博客
S
Schneier on Security
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
C
CXSECURITY Database RSS Feed - CXSecurity.com
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
D
DataBreaches.Net
Vercel News
Vercel News
P
Privacy International News Feed
D
Darknet – Hacking Tools, Hacker News & Cyber Security
C
Cyber Attacks, Cyber Crime and Cyber Security
J
Java Code Geeks
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
人人都是产品经理
人人都是产品经理
G
GRAHAM CLULEY
有赞技术团队
有赞技术团队
A
Arctic Wolf
I
InfoQ
T
Tor Project blog
Attack and Defense Labs
Attack and Defense Labs
Microsoft Security Blog
Microsoft Security Blog
博客园 - 司徒正美
K
Kaspersky official blog
Recent Commits to openclaw:main
Recent Commits to openclaw:main
Recent Announcements
Recent Announcements
D
Docker
TaoSecurity Blog
TaoSecurity Blog
G
Google Developers Blog
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
爱范儿
爱范儿
T
Troy Hunt's Blog
Help Net Security
Help Net Security
量子位
罗磊的独立博客
C
Cisco Blogs
Hacker News - Newest:
Hacker News - Newest: "LLM"
博客园 - 【当耐特】
腾讯CDC
Webroot Blog
Webroot Blog
N
News and Events Feed by Topic
小众软件
小众软件
N
Netflix TechBlog - Medium

博客园 - 小纸条

模型微调 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));
    }
}