区间相关问题的整理

区间相关问题的整理

掌握这些类型的区间问题,并且熟练掌握,就可以解决部分区间问题,当然不是全部,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;
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值