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

推荐订阅源

Engineering at Meta
Engineering at Meta
G
GRAHAM CLULEY
Attack and Defense Labs
Attack and Defense Labs
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
T
Tor Project blog
T
Threat Research - Cisco Blogs
阮一峰的网络日志
阮一峰的网络日志
H
Hackread – Cybersecurity News, Data Breaches, AI and More
Vercel News
Vercel News
Google DeepMind News
Google DeepMind News
U
Unit 42
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
Spread Privacy
Spread Privacy
C
CXSECURITY Database RSS Feed - CXSecurity.com
量子位
T
The Blog of Author Tim Ferriss
Project Zero
Project Zero
Webroot Blog
Webroot Blog
雷峰网
雷峰网
C
Cyber Attacks, Cyber Crime and Cyber Security
Microsoft Azure Blog
Microsoft Azure Blog
Microsoft Security Blog
Microsoft Security Blog
Scott Helme
Scott Helme
T
The Exploit Database - CXSecurity.com
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
A
About on SuperTechFans
NISL@THU
NISL@THU
AWS News Blog
AWS News Blog
Security Latest
Security Latest
S
Schneier on Security
W
WeLiveSecurity
K
Kaspersky official blog
有赞技术团队
有赞技术团队
Cyberwarzone
Cyberwarzone
P
Palo Alto Networks Blog
TaoSecurity Blog
TaoSecurity Blog
G
Google Developers Blog
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
Schneier on Security
Schneier on Security
博客园_首页
博客园 - 司徒正美
Application and Cybersecurity Blog
Application and Cybersecurity Blog
D
Darknet – Hacking Tools, Hacker News & Cyber Security
C
Check Point Blog
www.infosecurity-magazine.com
www.infosecurity-magazine.com
Recent Commits to openclaw:main
Recent Commits to openclaw:main
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
Know Your Adversary
Know Your Adversary
P
Privacy & Cybersecurity Law Blog
Hacker News - Newest:
Hacker News - Newest: "LLM"

博客园 - RonChen

距离和的最小值 归并排序与逆序对 快速排序与快速选择 同余分析 差分约束 Treap 点分治 莫队算法 分块 扫描线 错排问题 Sprague-Grundy (SG) 函数及其应用 容斥原理 卢卡斯定理 线性基 高斯消元 勒让德公式 次短路 分层图最短路 01 图最短路 洪水填充 双向搜索 迭代加深搜索 剪枝 最小表示法 表达式计算 KMP 算法 队列 高精度运算
区间合并
RonChen · 2026-07-20 · via 博客园 - RonChen

给定 \(N\) 个闭区间 \([l_i,r_i]\),要求将所有有交集(包括端点重合)的区间进行合并,最终计算出合并后互不相交的区间集合。

处理区间问题的核心往往在于消除无序性,如果区间是散乱分布的,很难判断一个区间究竟会与哪些区间发生合并。

通过将所有区间按照左端点从小到大进行排序,问题就会展现出明显的单调性。当从左到右依次处理排序后的区间时,任何一个新区间与当前维护的合并区间,只可能存在以下三种空间相对关系:

情况 1:完全包含(新区间在当前区间内部)
当前维护: [===================]
新区间:       [========]
处理:无须任何操作,右端点保持不变。

情况 2:交集相交(新区间延伸了当前区间)
当前维护: [===================]
新区间:               [==============]
处理:更新当前区间的右端点为新区间的右端点。

情况 3:完全脱离(两个区间无交集)
当前维护: [===================]
新区间:                            [========]
处理:当前维护的区间已经不可能再向右延伸。将其存入答案,并以新区间作为起点开始维护。

例题:P1496 火烧赤壁

\(n\) 个线段,每一个线段用 \([a_i, b_i]\) 表示,求所有线段的并集的总长度。
数据范围:\(n \le 20000, \ -2^{31} \le a_i \le b_i \le 2^{31}\),且 \(a_i\)\(b_i\) 都是整数。

解题思路

考虑两个有交集的线段,左端点靠左的那个是 \([a_1, b_1]\),另一个是 \([a_2, b_2]\),因为假定了 \(a_2 \ge a_1\),所以总的长度取决于 \(b_1\)\(b_2\) 的关系。如果 \(b_2 \le b_1\),那么第二个线段被第一个线段完全包含,合并后线段的左右端点和靠左的线段一模一样;如果 \(b_2 > b_1\),则相当于左端点不变的情况下,右端点在向右延展。

所以可以得到一个解法:将所有线段按左端点从小到达排序,如果相邻两个线段有交集,按上面的规则更新当前正在处理的线段的左右端点;如果相邻两个线段没有交集,则统计前面那个整体线段的长度,并将新的这个线段作为当前正在处理的线段。

参考代码
#include <cstdio>
#include <algorithm>
using namespace std;
const int N = 20005;
struct Fire {
    int l, r;
};
Fire a[N];
int main()
{
    int n; scanf("%d", &n);
    for (int i = 1; i <= n; i++) scanf("%d%d", &a[i].l, &a[i].r);
    // 按左端点排序
    sort(a + 1, a + n + 1, [](Fire f1, Fire f2) {
        return f1.l < f2.l;
    });
    int ans = 0, bg = a[1].l, ed = a[1].r; // 目前处理的燃烧段是[bg,ed]
    for (int i = 2; i <= n; i++) {
        if (a[i].l <= ed) ed = max(ed, a[i].r); // 与当前段有交叉
        else { // 与当前段无交叉
            // 答案加上上一段的燃烧总长,并将新的燃烧段端点设为a[i]的两个端点
            ans += ed - bg; bg = a[i].l; ed = a[i].r;
        }
    }
    ans += ed - bg; // 不要忘了处理最后一段
    printf("%d\n", ans);
    return 0;
}

习题:P1204 [USACO1.2] 挤牛奶Milking Cows

解题思路

这道题的目标是计算多个时间段合并后的“最长连续工作时间”和“最长连续空闲时间”,是一个经典的区间合并问题。

有一堆表示工作时间的区间 \([l,r]\),当多个区间有重叠时,它们可以被视为一个更长的、连续的工作时间段。例如 \([300,1000]\)\([700,1200]\) 重叠,可以合并成一个大的连续工作段 \([300,1200]\)。需要找到所有这样合并后形成的连续工作段中,最长的一个。同时,也需要找到这些合并后的连续工作段之间的空隙(无人工作时间),并找出其中最长的一个。

首先,将所有的区间(工作时段)按照它们的左端点(开始时间)从小到大排序。这是至关重要的一步,它保证了处理区间时是按照时间顺序向前推进的。

初始化一个“当前合并区间” \([bg,ed]\),它的值最初等于第一个(排序后)工作时段。对于接下来的每个工作时段 \(a_i\),有两种情况需要考虑。

  • 有重叠:如果当前工作时段 \(a_i\) 的开始时间 \(a_i.l\) 小于等于 \(ed\)(当前合并区间的结束时间),说明 \(a_i\) 与正在构建的连续工作段有重叠,就可以尝试合并它。合并操作也很简单,只需要更新 \(ed\)\(\max (ed, a_i.r)\),这是因为 \(a_i\) 可能会将连续工作段向右延伸。
  • 无重叠:如果 \(a_i.l\) 大于 \(ed\),说明 \(a_i\) 与当前的连续工作段之间出现了一个空隙。这意味着一个连续的工作段到 \(ed\) 就结束了,一个新的工作段从 \(a_i.l\) 开始。此时,需要计算刚刚结束的那个连续工作段的长度,并更新“最长连续工作时间”的答案。同时,也计算出了一个空闲时段,其长度为 \(a_i.l - ed\),用这个长度来更新“最长空闲时间”的答案。然后,将“当前合并区间” \([bg,ed]\) 重置为新的工作时段 \([a_i.l, a_i.r]\),准备开始构建下一个连续工作段。
参考代码
#include <cstdio>
#include <algorithm>
using namespace std;
const int N = 5005;
struct Work { // 定义工作时段结构体
    int l, r; // l:开始时间,r:结束时间
};
Work a[N]; // 结构体数组,存储所有工作时段
int main()
{
    int n;
    scanf("%d", &n); // 读取农民(工作时段)的数量
    for (int i = 1; i <= n; i++) { // 循环读取 n 个工作时段
        scanf("%d%d", &a[i].l, &a[i].r);
    }
    // 1. 将所有的工作时段按开始时间(左端点)从小到大排序
    sort(a + 1, a + n + 1, [](Work w1, Work w2) {
        return w1.l < w2.l;
    });
    // 2. 合并与计算
    int bg = a[1].l, ed = a[1].r; // [bg,ed]为当前正在工作的时段,刚开始等于第一个时间段
    int ans1 = ed - bg, ans2 = 0; // ans1为最长连续工作时间,ans2为最长连续空闲时间
    for (int i = 2; i <= n; i++) { // 从第二个工作时段开始遍历
        if (a[i].l <= ed) { // 说明a[i]这个工作时段与当前正在工作的时间段有交叉
            ed = max(ed, a[i].r); // 更新合并区间的结束时间,取两者中更晚的那个
            ans1 = max(ans1, ed - bg); // 可能需要更新最长连续工作时长
        } else { // 与正在工作的时间段没有交叉,出现空隙
            ans2 = max(ans2, a[i].l - ed); // 计算刚刚结束的空闲时段的长度,并更新最长空闲时间
            bg = a[i].l; ed = a[i].r; // 重置合并区间,开始一个新的连续工作段
            ans1 = max(ans1, ed - bg); // 可能需要更新最长连续工作时长
        }
    }
    printf("%d %d\n", ans1, ans2); // 输出最终结果
    return 0;
}