与区间有关的问题有很多,大部分都是用贪心的策略去解决,不是按照左端点排序,就是按照右端点排序,或者按照左右端点双关键字排序。先讲一下按照左端点进行排序的,其他的后续跟新。
区间合并的应用场景:给出很多个区间,把有交集的区间合并为一个【特殊规定:端点处相交,也算有交集】。
这类题目往往让我们输出最终的区间数目或者区间长度的最值,又或者是将所有区间合并的最小花费。要求我们快速实现把有交集的区间合并为一个区间。
规定左端点为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
/*#include 把所有的区间存到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
#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;
}