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

推荐订阅源

奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
WordPress大学
WordPress大学
博客园 - 【当耐特】
Engineering at Meta
Engineering at Meta
IT之家
IT之家
Apple Machine Learning Research
Apple Machine Learning Research
小众软件
小众软件
美团技术团队
S
SegmentFault 最新的问题
The GitHub Blog
The GitHub Blog
Martin Fowler
Martin Fowler
Recorded Future
Recorded Future
H
Help Net Security
aimingoo的专栏
aimingoo的专栏
Y
Y Combinator Blog
博客园_首页
A
About on SuperTechFans
MongoDB | Blog
MongoDB | Blog
阮一峰的网络日志
阮一峰的网络日志
V
Visual Studio Blog
D
DataBreaches.Net
人人都是产品经理
人人都是产品经理
大猫的无限游戏
大猫的无限游戏
B
Blog
The Register - Security
The Register - Security
I
InfoQ
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
雷峰网
雷峰网
Last Week in AI
Last Week in AI
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
月光博客
月光博客
酷 壳 – CoolShell
酷 壳 – CoolShell
Blog — PlanetScale
Blog — PlanetScale
N
Netflix TechBlog - Medium
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
有赞技术团队
有赞技术团队
Stack Overflow Blog
Stack Overflow Blog
Jina AI
Jina AI
Security Archives - TechRepublic
Security Archives - TechRepublic
Hacker News - Newest:
Hacker News - Newest: "LLM"
U
Unit 42
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
W
WeLiveSecurity
Latest news
Latest news
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
博客园 - 叶小钗
L
Lohrmann on Cybersecurity
博客园 - Franky
Recent Commits to openclaw:main
Recent Commits to openclaw:main
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO

博客园 - 小纸条

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