AcWing 906 区间分组

题目描述:

给定N个闭区间[ai,bi],请你将这些区间分成若干组,使得每组内部的区间两两之间(包括端点)没有交集,并使得组数尽可能小。

输出最小组数。

输入格式

第一行包含整数N,表示区间数。接下来N行,每行包含两个整数ai,bi,表示一个区间的两个端点。

输出格式

输出一个整数,表示最小组数。

数据范围

1≤N≤10^5,
−10^9≤ai≤bi≤10^9

输入样例:

3
-1 1
2 4
3 5

输出样例:

2

分析:

本题是AcWing 111 畜栏预定的简化版,要求给区间分组,组内区间都没有交集,求最小的分组数。贪心问题一般需要先假设一种贪心策略,然后从直观层面上理解这种策略的正确性,最后才从理论上去证明这种策略是最优的。

这里的策略是先按照区间左端点从小到大排序,按照左端点或者是右端点排序是完全对称的,只不过排序方法不同,最后扫描的方向也不同。然后选择第一个区间加入分组,接着遍历第二个区间,如果第二个区间左端点大于已有分组中区间右端点的最小值,则将其加入该分组,并更新该分组的右端点最大值,否则新建一个分组,后面的遍历也采取相同的策略。

举个例子,分组1:【1,3】,【5,6】;分组2:【2,4】,可以发现分组内部的区间是严格不相交的,每个区间的左端点都严格的大于上一个区间的右端点,紧接着遍历到【7,9】,这时候分组一右端点最大值是6,分组二3右端点最大值是4,当前区间的左端点7大于这两个分组的右端点最大值,所以选择其中任意一个即可。我们选择其中右端点较小的那个,或许有人会想,既然既可以加入分组1,又可以加入分组2,那么为什么不加入右端点较大的那个分组呢,加入分组1将分组1的右端点扩大到9,而分组2右端点依旧是4,把较小的右端点留给后面的分组不是更容易与后面的分组不相交,从而使分组数更少呢?事实上,区间已然按照左端点从小到大排序了,【7,9】后面区间的左端点必然大于7,也就是说,原分组中小于7的右端点必然也小于后面区间的左端点,因此,加入的分组是任意的,只要不相交即可。至于为什么选择分组中右端点最小的那个加入,是因为,多个分组中有一些是可以加入的,另一些由于分组右端点较大不能加入,我们只需要先与分组中右端点较小的进行比较,大于各个分组中最小的那个右端点说明可以加入,否则当前区间左端点要小于所有分组的右端点,为了不相交只能新建分组。为了更便捷的找到各个分组中最后一个区间的最小右端点,可以将每个分组的最后一个区间的右端点用一个小根堆维护,这样,可以每次在O(1)的时间内找到最小的右端点。

总而言之,本题的策略是先按照区间的左端点从小到大排序,然后扫描各个区间,如果当前没有分组或者当前区间的左端点要小于所有分组的右端点,则新建一个分组,将其右端点加入到小根堆中;如果所有分组中最后一个区间最小的右端点要小于当前区间的左端点,则将当前区间加入该分组,同时更新小根堆里面当前分组的最有一个区间的右端点,具体实现为先删除小根堆堆顶元素,再插入新的右端点,更新小根堆的时间复杂度是logn级别的。

我们已经给出了本题的贪心策略,并且从直观上说明了为什么排序后要选择所有分组中最后一个区间右端点较小的分组加入。下面给出该策略求得的分组数是最小的理论证明。我们知道,扫描区间的过程,如果需要新建一个分组,除了当前没有分组的情况外,就是当前所有分组的最后一个区间的右端点都不小于当前遍历到区间的左端点,而当前区间的左端点又大于之前分组区间的左端点,这意味着,只有当前区间与之前所有分组的最后一个区间都相交,才会新建一个分组。设该策略一共新建了ans个分组,则在新建最后一个分组时,当前区间一定与其它ans - 1个分组的最后一个区间都相交,或者说,当前区间是另外ans - 1个区间的公共区间。如下图所示:

新建第四个分组时,当前区间的左端点必然大于其它三个分组最后一个区间的左端点,且右端点要不大于其它三个区间的右端点,上面的四个区间,由于有公共交点,所以至少要分成四组才能确保不相交,因此该策略求得的分组数是最少的。

#include <iostream>
#include <algorithm>
#include <queue>
#include <vector>
using namespace std;
const int maxn = 100005;
pair<int,int> a[maxn];
priority_queue<int> res;
int main(){
    int n,ans = 0;
    cin>>n;
    for(int i = 0;i < n;i++)    cin>>a[i].first>>a[i].second;
    sort(a,a + n);
    res.push(-a[0].second);//存相反数产生小根堆的功效
    for(int i = 1,j;i < n;i++){
        if(res.size() && -res.top() < a[i].first) res.pop();
        res.push(-a[i].second);
    }
    cout<<res.size()<<endl;
    return 0;
}
发布了272 篇原创文章 · 获赞 26 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/qq_30277239/article/details/104001511