区间合并算法
两个区间的关系
两个区间的关系无非就下面几种(这里说的左端点是指下面那个区间的左右端点,内外指的是下面区间相较于上面区间的位置关系)
左端点:1.区间内 2.区间外
右端点:1.区间内 2.区间外
左1右1,左1右2,左2右1,左2右2
左2右1这种情况是不可能的,所以一共就3种可能的关系
|_____________|
1.|______|
2.|___________|
3. |___________|
针对3种不同的情况,需要做出不同的操作
合并的策略
所谓区间合并就是将所有存在交集(只有端点相等也算相交)的区间进行合并
我们始终维护一个区间,在遍历所有区间过程中,针对不同的情况做出不同的处理
上面情况1:我们维护的区间不发生变化,还是最上面的区间
情况2:维护的区间右端点需要扩大到此区间的右端点
情况3:和最上面的区间相比,不存在交集,所以维护区间的左右端点全部更新为当前区间的左右端点
模板
// 将所有存在交集的区间合并
void merge(vector<PII> &segs)
{
vector<PII> res;
sort(segs.begin(), segs.end());
int st = -2e9, ed = -2e9; // 初始值的选取非常巧妙且关键
for (auto seg : segs)
if (ed < seg.first)
{
if (st != -2e9) res.push_back({st, ed}); // 判断的意义在于保证所有能够进入答案的区间不是我们定义的初始区间
st = seg.first, ed = seg.second;
}
else ed = max(ed, seg.second);
if (st != -2e9) res.push_back({st, ed}); // 判断的意义同上,这里单独处理一下是因为最后一个区间或者只有一个区间时无法进入答案
segs = res;
}
应用举例
问题描述
给定 n 个区间 \([li,ri],\) 要求合并所有有交集的区间。
注意如果在端点处相交,也算有交集。
输出合并完成后的区间个数。
例如:[1,3]和[2,6]可以合并为一个区间[1,6]。
输入格式
第一行包含整数n。
接下来n行,每行包含两个整数 l 和 r。
输出格式
共一行,包含一个整数,表示合并区间完成后的区间个数。
数据范围
\(1 <= N <= 100000\)
\(-10^9 <= L_i <= r_i <= 10^9\)
输入样例
5 1 2 2 4 5 6 7 8 7 9
输出样例
3
解决代码
// 做法1:不真的进行区间合并,只是计数
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
int n;
vector<pair<int, int>> segs;
int main()
{
cin >> n;
for (int i = 0; i < n; ++ i)
{
int l, r;
cin >> l >> r;
segs.push_back({l, r});
}
sort(segs.begin(), segs.end()); // sort对pair排序默认是首先按照第一关键字排序,之后按照第二关键字排序
int st = -2e9, ed = -2e9; // 初始值的设计很关键
int res = 0;
for (auto seg : segs)
{
int l = seg.first, r = seg.second;
if (l > ed) // 上面的情况3
{
st = l;
ed = r;
++ res;
}
else ed = max(ed, r); // 情况1和2
}
cout << res << endl;
}
// 做法2:真的把区间进行合并,然后计算区间个数
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
int n;
vector<pair<int, int>> segs;
// 需要注意的是能够放进答案的区间一会不能是我们定的初始区间,即if (st != -2e9)的必要性
void merge(vector<pair<int, int>> &segs)
{
vector<pair<int, int>> res;
int st = -2e9, ed = -2e9;
for (auto seg : segs)
{
int l = seg.first, r = seg.second;
if (l > ed)
{
if (st != -2e9) res.push_back({st, ed}); // 当遍历到一个没有交集的区间时要把前一个区间放进答案,但要看是不是初始值
st = l;
ed = r;
}
else ed = max(ed, r);
}
// 循环中把一个区间放进答案的条件是出现新的无交集的区间,如果本身就一个区间或者到最后一个区间,循环中肯定不会把这个区间放进答案,所以需要单独处理一下最后的一个区间
if (st != -2e9) res.push_back({st, ed}); // 因为segs中可能没有区间
segs = res;
}
int main()
{
cin >> n;
for (int i = 0; i < n; ++ i)
{
int l, r;
cin >> l >> r;
segs.push_back({l, r});
}
sort(segs.begin(), segs.end());
merge(segs);
cout << segs.size() << endl;
return 0;
}