区间相关问题的整理
掌握这些类型的区间问题,并且熟练掌握,就可以解决部分区间问题,当然不是全部,hhh,在这里我整理了一下相关区间问题的模板题,因为目前刷的题目还比较少,如果后续遇到其他类型的区间题目,也会更新!如果有存在问题希望大家能够指出。
目录:
类型:
1)最少点覆盖所有区间问题(区间选点)
模板题目:
给定 N 个闭区间 [ai,bi],请你将这些区间分成若干组,使得每组内部的区间两两之间(包括端点)没有交集,并使得组数尽可能小。
输出最小组数。
输入格式:
第一行包含整数 N,表示区间数。
接下来 N 行,每行包含两个整数 ai,bi,表示一个区间的两个端点。
输出格式:
输出一个整数,表示最小组数。
数据范围:
1≤N≤10^5,
−109≤ai≤bi≤10^9
输入样例:
3
-1 1
2 4
3 5
输出样例:
2
时间复杂度:O(nlog2n)
思路:
将所有区间按照右端点从小到大排序,令last为当前选取区间的右端点,初始值为无穷大,需要的点数cnt = 0。然后遍历所有区间,如果遍历到的区间左端点大于当前选取区间的右端点last,那么肯定需要两个点才能够包含这两段区间,需要点数cnt+1,将last更新成该遍历到的区间的右端点;如果遍历到的区间左端点小于等于当前选取区间的右端点last,那么需要1个点即可同时覆盖这两个区间,last仍然指向当前区间右端点。最终遍历完所有区间后cnt的值即是我们的答案。
代码:
#include<algorithm>
#include<iostream>
using namespace std;
const int N = 1e5+5;
struct Segment{
int l, r;
}segment[N];
int n;
bool cmp(Segment x,Segment y) //自定义结构体排序,按照右端点从小到大排序
{
return x.r<y.r;
}
int main()
{
cin>>n;
for(int i = 0; i < n; i ++)cin>>segment[i].l>>segment[i].r;
sort(segment, segment+n,cmp); //以区间的右端点从小到大排序
int last= - 1e20; //当前选取区间的右端点
int cnt = 0; //需要点的数量
for(int i = 0; i < n; i ++){
if(last < segment[i].l) //无法通过1个点同时覆盖当前区间以及遍历到的区间
{
cnt++;
last=segment[i].r;
}
}
cout<<cnt<<endl;
return 0;
}
2)选取不相交区间最大数量(最大不相交区间数量)
模板题目
给定 N 个闭区间 [ai,bi],请你在数轴上选择若干区间,使得选中的区间之间互不相交(包括端点)。
输出可选取区间的最大数量。
输入格式
第一行包含整数 N,表示区间数。
接下来 N 行,每行包含两个整数 ai,bi,表示一个区间的两个端点。
输出格式
输出一个整数,表示可选取区间的最大数量。
数据范围
1≤N≤10^5,
−109≤ai≤bi≤10^9
输入样例:
3
-1 1
2 4
3 5
输出样例:
2
时间复杂度:O(nlog2n)
思路:
因为要选取做多不相交的区间数量,因此我们在选第一个区间的时候要让其右端点最小,因为这样选就可以让后面放最多的区间数量,实现局部最优,接着每一步我们都选择不与之前选取区间相交,且右端点最小的区间,利用贪心的思想,每次我们选取的区间都是局部状态的最优,从而实现整体的最优。
具体实现方法:将所有区间按照右端点从小到大排序,令last为选取区间的右端点,初始值为无穷大,然后遍历所有区间,如果遍历到的区间左端点大于last,即这两个区间不相交,可以放进来,那么我们就更新last值为该区间右端点,并继续往后遍历。经过上述操作后,选出的不相交区间的数量是最大的。
代码:
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
#include<cmath>
using namespace std;
typedef long long ll;
typedef pair<int,int> PII;
const int INF = -1e9;
const int N = 1e5+5;
struct Segment{
int l, r;
}segment[N];
bool cmp(Segment x, Segment y){
return x.r<y.r;
}
int n;
int main()
{
cin>>n;
for(int i = 0; i < n; i ++)cin>>segment[i].l>>segment[i].r;
sort(segment, segment + n, cmp); //以区间的右端点从小到大排序
int last = - 2e9; //当前选取区间的右端点
int cnt=0; //不想交的区间数
for(int i = 0; i < n; i ++){
if(last < segment[i].l){
cnt++;
last = segment[i].r;
}
}
cout<<cnt<<endl;
return 0;
}
3)组内区间两两不相交的最小组数(区间分组)
模板题目:
给定 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
方法一:O(nlog2n)
思路:
题目要求区间分组,组内区间不相交的前提下,分成尽可能少的组。
首先我们把区间根据左端点从小到大排序。
然后开一个小根堆的队列(小的数字在前),用来存放每一组的右端点,这样就能保证各组中最小右端点的那组先放入区间,可以保证使得组数最少。根据左端点从小到大的顺序去遍历各个区间,如果队列中没有任何区间就将第一个区间的右端点直接放入队列中。之后就可以分为两种情况:
1、如果放入的区间的左端点小于或等于队列中 最小右端点,那么就是说现在队列中没有任何一组可以放入该区间,因此我们要开辟一个新的区间,我们就直接将该区间的右端点加入队列中。
2、如果放入的区间的左端点大于队列中最小的右端点,就是说,拥有该最小右端点的组可以容纳这个区间,那么我们就把这个区间加入到这一组中,具体操作就是将这个最小的右端点移除,然后加入当前区间的右端点,相当于是对这一组的右端点进行了更新。
代码:
#include <iostream>
#include <algorithm>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <queue>
using namespace std;
typedef long long ll;
typedef pair<int, int> PII;
const int N = 1e5 + 5;
int n;
PII a[N];
priority_queue<int, vector<int>, greater<int> >qu;
int main()
{
cin>>n;
for(int i=0;i<n;i++)cin>>a[i].first>>a[i].second;
sort(a,a+n); //对区间根据左端点从小到大进行排序
for(int i=0;i<n;i++){
//需要加入新的组
if(qu.empty()||qu.top()>=a[i].first)qu.push(a[i].second);
//在原有的组上加入区间,并更新右端点
else{
qu.pop();
qu.push(a[i].second);
}
}
cout<<qu.size()<<endl;//输出组数
return 0;
}
方法二:O(n)
思路:
对于有重叠的部分,我们肯定要将他们分成不同的组,因此我们只要找最大的重叠区间数即可。
代码:
#include <iostream>
#include <algorithm>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <map>
using namespace std;
typedef long long ll;
map<int,int>mp;
int n,l,r;
int main()
{
cin>>n;
while(n--){
cin>>l>>r;
mp[l]++;
mp[r+1]--;
}
int ans=0,cnt=0;
for(auto i=mp.begin();i!=mp.end();i++){
cnt+=i->second;
ans=max(ans,cnt);
}
cout<<ans<<endl;
return 0;
}
4)选择最少区间覆盖线段区间(区间覆盖)
模板题目
给定 N 个闭区间 [ai,bi] 以及一个线段区间 [s,t],请你选择尽量少的区间,将指定线段区间完全覆盖。
输出最少区间数,如果无法完全覆盖则输出 −1。
输入格式
第一行包含两个整数 s 和 t,表示给定线段区间的两个端点。
第二行包含整数 N,表示给定区间数。
接下来 N 行,每行包含两个整数 ai,bi,表示一个区间的两个端点。
输出格式
输出一个整数,表示所需最少区间数。
如果无解,则输出 −1。
数据范围
1≤N≤10^5,
−109≤ai≤bi≤109,
−109≤s≤t≤109
输入样例:
1 5
3
-1 3
2 4
3 5
输出样例:
2
时间复杂度:O(nlog2n)
思路:
做法:
1、将所有区间按左端点从小到大排序
2、从前往后依次枚举每个区间,在所有能覆盖start的区间中,选择右端点最大的区间(即区间左端点小于等于start的所有区间里肯定是选右端点最大的覆盖的线段区域更大!)然后将start更新成右端点的最大值。(满足覆盖条件的同时要保证最长)
代码:
#include <iostream>
#include <algorithm>
#include <cstdio>
#include <cstring>
#include <cmath>
using namespace std;
typedef long long ll;
typedef pair<int, int> PII;
const int INF = 1e9;
const int N = 1e5 + 5;
int n,st,ed,ans=0;
PII a[N];
int main()
{
cin>>st>>ed;
cin>>n;
for(int i=0;i<n;i++)cin>>a[i].first>>a[i].second;
sort(a,a+n);//按左端点排序
for(int i=0;i<n;i++){
//通过双指针找可以覆盖st并且右端点最长的区间
int j=i,r=-2e9;
while(j<n&&a[j].first<=st){
r=max(r,a[j].second);
j++;
}
if(r<st)//当前右端点的最远位置不能到达下一线段的左端点,一定不能覆盖
{
cout<<"-1"<<endl;
return 0;
}
ans++;//区间数加1
if(r>=ed)//完成了区间覆盖
{
cout<<ans<<endl;
return 0;
}
st=r;
}
cout<<"-1"<<endl;//之前没有退出,说明取遍所有点虽然都可以相连但无法覆盖到ed(最后一点)
return 0;
}