题目传送门

一、读懂题目

\(n\)个区间\(a[i]\)~\(b[i]\),将这些区间进行分组操作,要求每组内部的区间不能存在交集(有一个点共享也不行!),求最小组数。

注意一点,只要组内所有的区间不存在交集就可以算是一个组,详情看下图:
AcWing 906. 区间分组

你看,这道题很神奇吧,别看有那么多的区间,其实按照题目要求进行分组,最后分的组可能不是你想象的那样。

二、实现思路

AcWing 906. 区间分组
  1. 使用小顶堆记录已经创建的组,组的值用它最后一个成员的右边界表示:x.r

  2. \(heap.top()\) 表示已有组的最小右边界,这就意味着,其它组的右边界肯定比这个值要大,如果遍历到的区间左端点小于等于最小的右边界(就是有交叉的意思),再加上现在是按左端点排的序,意味着比它左边小的都已经入了小顶堆,它和最小的有冲突,必须也和其它的有冲突,直接创建新组!

  3. 不冲突的话,加入到当前组中!默认把该区间加入到右边界最小的分组中,方法:取出这个分组的右边界,并用 \(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\n", 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;
}

相关文章: