区间合并模板【彩色图示版&附例题+AC代码(全注释)】

与区间有关的问题有很多,大部分都是用贪心的策略去解决,不是按照左端点排序,就是按照右端点排序,或者按照左右端点双关键字排序。先讲一下按照左端点进行排序的,其他的后续跟新

区间合并的应用场景:给出很多个区间,把有交集的区间合并为一个【特殊规定:端点处相交,也算有交集】。

这类题目往往让我们输出最终的区间数目或者区间长度的最值,又或者是将所有区间合并的最小花费。要求我们快速实现把有交集的区间合并为一个区间。

规定左端点为st,右端点为ed。按照左端点排序,进行区间合并时会遇到下面4种情况,还会遇到特殊的边界处理(规定:端点处相交,也算有交集

在这里插入图片描述
下面用一道例题来消化一下:

原题 “区间和并” 链接
给定 n 个区间 [li,ri],要求合并所有有交集的区间。
注意如果在端点处相交,也算有交集。
输出合并完成后的区间个数。
例如:[1,3]和[2,6]可以合并为一个区间[1,6]。

输入格式
第一行包含整数n。
接下来n行,每行包含两个整数 l 和 r。

输出格式
共一行,包含一个整数,表示合并区间完成后的区间个数。

数据范围
1 ≤ n ≤ 100000,
−1e9 ≤ li ≤ ri ≤1e9
输入样例:
5
1 2
2 4
5 6
7 8
7 9
输出样例:
3

样例的模拟过程:
在这里插入图片描述

具体步骤看行注释说明,看不懂评论区call我 QAQ

#include <bits/stdc++.h>
/*#include <vector> 把所有的区间存到vector里面去,
用pair存储,first存区间的左端点,second存右端点 */
using namespace std;
typedef pair<int,int> PII; //定义存储区间左右端点的pair 
const int N = 100010; 

int n;
vector<PII> segs; //定义一个变量,用以存储所有区间 

void merge(vector<PII> &segs) //合并区间的功能函数 
{
    
    
	//pair排序在c++中会优先以左端点,再以右端点排序 
	vector<PII> res;
	sort(segs.begin(),segs.end()); //把所有区间按照要求排序 
	
	int st=-2e9,ed=-2e9; //刚开始时没有任何区间,设立2个边界值(>题目数据范围的临界值)用作初始区间 
	for(auto seg : segs) //从前往后扫描所有的区间 
	{
    
    
		if(ed<seg.first) //维护(第一次比较时为初始的)的在枚举的区间的左边 的情况下(也就是没有交集的时候 
		{
    
    
			if(st!=-2e9)  res.push_back({
    
    st,ed}); //非初始(边界值)时,结果+1 
			st=seg.first,ed=seg.second; //无法合并时更新区间(第一次是把初始区间更新为排序后的第一个区间 
		}
		else ed=max(ed,seg.second); //求并集,取维护的与当前枚举的区间的右端点的最大值 
	}
	
	if(st!=-2e9)  res.push_back({
    
    st,ed}); //把最后剩下的区间加到答案里(防止输出为空 
	segs=res; //把区间更新成res 
}

int main()
{
    
    
	cin>>n;
	for(int i=0; i<n; i++)
	{
    
    
		int l,r;
		cin>>l>>r;
		segs.push_back({
    
    l,r}); //依次存储各个区间 
	}
	
	merge(segs); //合并区间 
	
	cout<<segs.size()<<endl; //输出合并之后的区间数目 
	return 0;
}

应用与拓展:

原题 “格子染色” 链接 【美团2019笔试题】
在二维平面上有一个无限的网格图形,初始状态下,所有的格子都是空白的。
现在有n个操作,每个操作是选择一行或一列,并在这行或这列上选择两个端点网格,把以这两个网格为端点的区间内的所有网格染黑(包含这两个端点)。
问经过n次操作以后,共有多少个格子被染黑,重复染色同一格子只计数一次。

输入格式
第一行包含一个正整数n。
接下来n行,每行包含四个整数x1,y1,x2,y2,表示一个操作的两端格子坐标。
若x1=x2则是在一列上染色,若y1=y2则是在一行上染色。
保证每次操作是在一行或一列上染色。

输出格式
包含一个正整数,表示被染黑的格子的数量。

数据范围
1≤n≤10000,
−1e9≤ x1,y1,x2,y2 ≤1e9
输入样例:
3
1 2 3 2
2 5 2 3
1 4 3 4
输出样例:
8

思路:同行同列合并区间并且计算染黑的总数(多于实际),然后每行每列判断是否相交,相交则减

#include <bits/stdc++.h>
#define ll long long  
using namespace std;
const int N = 10010;

struct Segment
{
    
    
    int k;//行号或列号
    int l,r;//左右坐标
    bool operator < (const Segment& w)const //重载 先按行/列,再按左右端点
	{
    
    
        if(k!=w.k)  return k<w.k;
        if(l!=w.l)  return l<w.l;
        return r<w.r;
    }
};

ll merge(vector<Segment> &q)
{
    
    
    vector<Segment> w; //合并的临时对象
    sort(q.begin(),q.end()); //O(nlogn)
    ll res = 0;
    for(int i=0; i<q.size();) //+
	{
    
    
        int j=i;
        while(j<q.size() && q[j].k==q[i].k) j++; //相同行/列坐标范围
        int l=-2e9, r=l-1;
         for(int k=i; k<j; k++) //+,++是O(n)
		 {
    
     
            if(r<q[k].l) 
			{
    
    
                res += r-l+1;
                if(l!=-2e9)  w.push_back({
    
    q[i].k, l, r});
                l=q[k].l, r=q[k].r;
            } 
			else r=max(q[k].r,r);
        }
        if(l!=-2e9)  w.push_back({
    
    q[i].k, l,r}); //做后一组塞进去
        res += r-l+1;
        i = j;
    }
    q = w;//拷贝回来 直接等于号就行
    return res;
}

int main()
{
    
    
    int n;
    cin >> n;
    vector<Segment> rows, cols;

    while (n--)
    {
    
    
        int x1, y1, x2, y2;
        cin >> x1 >> y1 >> x2 >> y2;
        if(x1==x2)  cols.push_back({
    
    x1,min(y1,y2), max(y1,y2)});
        else  rows.push_back({
    
    y1, min(x1,x2), max(x1,x2)});
    }

    ll res = merge(rows)+merge(cols);
    for(auto r:rows)
        for(auto c:cols)
            if(r.k>=c.l && r.k<=c.r && c.k>=r.l && c.k<=r.r) //判断是否在范围内 //O(n^2)
                res--;
                
    cout << res << endl;
    return 0;
}

猜你喜欢

转载自blog.csdn.net/Luoxiaobaia/article/details/107882989