一、读懂题目
有(n)个区间(a[i])~(b[i]),将这些区间进行分组操作,要求每组内部的区间不能存在交集(有一个点共享也不行!),求最小组数。
注意一点,只要组内所有的区间不存在交集就可以算是一个组,详情看下图:
你看,这道题很神奇吧,别看有那么多的区间,其实按照题目要求进行分组,最后分的组可能不是你想象的那样。
二、实现思路
-
使用小顶堆记录已经创建的组,组的值用它最后一个成员的右边界表示:
x.r
。 -
(heap.top()) 表示已有组的最小右边界,这就意味着,其它组的右边界肯定比这个值要大,如果遍历到的区间左端点小于等于最小的右边界(就是有交叉的意思),再加上现在是按左端点排的序,意味着比它左边小的都已经入了小顶堆,它和最小的有冲突,必须也和其它的有冲突,直接创建新组!
-
不冲突的话,加入到当前组中!默认把该区间加入到右边界最小的分组中,方法:取出这个分组的右边界,并用 (range[i].r) 更新这一分组的右边界。
三、实现代码
#include <bits/stdc++.h>
using namespace std;
const int N = 100010;
int n;
//用一个小顶堆来存储到底有多少个组,小顶堆记录的是组的最后端点位置
priority_queue<int, vector<int>, greater<int>> heap;
struct Range {
int l, r;
} range[N];
//强制要求使用这种结构体的排序自定义函数方式
//按每个区间的左端点从小到大排序
bool cmp(const Range &a, const Range &b) {
return a.l < b.l;
}
int main() {
//优化输入
ios::sync_with_stdio(false);
cin >> n;
for (int i = 0; i < n; i++) {
int l, r;
cin >> l >> r;
range[i] = {l, r};
}
sort(range, range + n);
//遍历每个区间
for (int i = 0; i < n; i++) {
auto x = range[i];
//空的,或者有交集(冲突)开辟新的组
if (heap.empty() || heap.top() >= x.l) heap.push(x.r);
else {
//合并到旧的组
heap.pop();
heap.push(x.r);
}
}
//结果是一共有多少个组
printf("%d
", heap.size());
return 0;
}
四、另类思路
看了一下,貌似是求最大区间厚度的问题。可以把这个问题想象成活动安排问题。
有若干个活动,第(i)个活动开始时间和结束时间是([S_i,F_i]),同一个教室安排的活动之间不能交叠,求要安排所有活动,少需要几个教室?
有时间冲突的活动不能安排在同一间教室,与该问题的限制条件相同,即最小需要的教室个数即为该题答案。
我们可以把所有开始时间和结束时间排序,遇到开始时间就把需要的教室加(1),遇到结束时间就把需要的教室减(1),在一系列需要的教室个数变化的过程中,峰值就是多同时进行的活动数,也是我们至少需要的教室数。
#include <bits/stdc++.h>
using namespace std;
const int N = 100100;
int b[2 * N]; //key,value:第几个端点,坐标值
int idx; //用于维护数组b的游标
int n; //共几个区间
int res = 1; //全放到一个组中,最小,默认值1
int main() {
//优化输入
ios::sync_with_stdio(false);
//n个区间
cin >> n;
for (int i = 0; i < n; i++) {
int l, r;
cin >> l >> r;
b[idx++] = l * 2; //标记左端点为偶数;同比放大2倍,还不影响排序的位置,牛~
b[idx++] = r * 2 + 1; //标记右端点为奇数;同比放大2倍,还不影响排序的位置,牛~
}
//将所有端点放在一起排序,由小到大
sort(b, b + idx);
int t = 0;
for (int i = 0; i < idx; i++) {
if (b[i] % 2 == 0) t++; //左端点
else t--; //右端点
res = max(res, t); //动态计算什么时间点时,出现左的个数减去右的个数差最大,就是冲突最多的时刻
}
//输出结果
printf("%d", res);
return 0;
}
上面的代码是不允许存在任何两个结点开始与结束在同一个点的,比如1到3点,3到4点,算冲突。有时这样的不算冲突,就需要另一种方案:
如果能区间端点能重合的话,是不是端点标记的奇数偶数反一下就行了。
#include <bits/stdc++.h>
using namespace std;
const int N = 100100;
int b[2 * N]; //key,value:第几个端点,坐标值
int idx; //用于维护数组b的游标
int n; //共几个区间
int res = 1; //全放到一个组中,最小,默认值1
int main() {
//优化输入
ios::sync_with_stdio(false);
//n个区间
cin >> n;
for (int i = 0; i < n; i++) {
int l, r;
cin >> l >> r;
b[idx++] = l * 2 + 1; //标记左端点为奇数;同比放大2倍,还不影响排序的位置,牛~
b[idx++] = r * 2; //标记右端点为偶数;同比放大2倍,还不影响排序的位置,牛~
}
//将所有端点放在一起排序,由小到大
sort(b, b + idx);
int t = 0;
for (int i = 0; i < idx; i++) {
if (b[i] % 2) t++; //左端点
else t--; //右端点
res = max(res, t); //动态计算什么时间点时,出现左的个数减去右的个数差最大,就是冲突最多的时刻
}
//输出结果
printf("%d", res);
return 0;
}