1.区间问题
1.区间选点
算法思路:
1.先将所有区间以右端点升序排序
如果以左端点升序排序,则会出现
区间a在区间b的上方,所以优先选择a区间的右端点,那么就会导致区间b中不包含任何点。
2.将定位到的区间右端点和接下来的区间左端点比较,如果找到一个区间左端点比定位区间右端点大,
此时说明两区间中间有空档,将定位移到该区间右端点,直到循环结束。
3.问题等价于求最多多少个不相交的区间的个数。
证明正确性:假定我们的答案是ans,求出来的是cnt,要想证明cnt = ans ,只要证明ans≤并且ans≥cnt。
(1)显然ans≤cnt;
(2)因为自增cnt时的条件是两区间无交集,因此标记这些不相交的区间是标记所有区间的必要条件,所以最终的答案一定要大于等于已经标记的次数,即ans≥cnt。
得证:ans=cnt
#include <iostream>
#include <algorithm>
using namespace std;
typedef pair<int , int> PII;
const int N = 100010;
PII range[N];//first是右端点 second是左端点
int ans;
int main()
{
int n;
cin >> n;
for(int i = 0 ; i < n ; i ++)
cin >> range[i]. second >> range[i].first;
sort(range , range + n);
int r = -2e9;
for(int i = 0; i < n ; i++)
{
if(range[i].second > r)
{
ans++;
r = range[i].first;
}
}
cout << ans << endl;
return 0;
}
2.最大不相交区间数量
算法思想:代码与区间选点一模一样
证明正确性:
假设最后数量的最大是ans,求得的数量是res。
我们根据上一题求出来res个点,说明最多有res个区间不相交。如果存在ans个区间不相交,且ans>res,则应该选出ans个点来使踏遍每一个区间,但是已经求出res个点足以满足踏遍所有点,说明不存在ans个不相交区间。
代码同上
3.区间分组
题目:给定N个闭区间[ai,bi],请你将这些区间分成若干组,使得每组内部的区间两两之间(包括端点)没有交集,并使得组数尽可能小。
算法思路:
1.先将区间以左端点升序排序;
2.声明一个小根堆来维护每个组最右边的右端点
3.每次判断新的区间的左端点range[i].first和小根堆中的右端点heap.top(),
如果heap.pop()>=range[i].first,说明不存在一个组能容下新区间,需要超新创建一个组;
否则将新区间放到右端点最小的组,即最右端点是heap.top()的组(只要能容下新区间的组都可以放,这里为了方便直接放到右端点最小的组)。
4.更新新区间加入的/创建的组的右端点
(1)新建组,直接将range[i].second推入堆中;
(2)加入到已有组,将该组的最右端点弹出堆,并将新区间的右端点推入即可。
证明正确性:设最小区间是ans,求出来的区间是cnt
1.显然ans <= cnt
2.当我们开到cnt-1个组合时,若接下来的区间的左端点≤所有组的最右右端点,此时需要再开一个组合,这时range[i].first和cnt个组都有交集(因为是以左端点升序排序,所以不存在新区间的右端点比已有组中某个区间的左端点小的情况),即这cnt个组合有公共点,因此至少需要cnt个组,即ans≥cnt.
得证:ans = cnt
#include <iostream>
#include <queue>
#include <algorithm>
using namespace std;
const int N = 100010;
int n;
pair<int , int> range[N];
int main()
{
cin >> n;
for(int i = 0 ; i < n ; i++)
cin >> range[i].first >> range[i].second;
sort(range , range + n);
priority_queue<int , vector<int> , greater<int>> heap;
for(int i = 0 ; i < n ; i++)
{
if(heap.empty() || heap.top() >= range[i].first) ;
else heap.pop();
heap.push(range[i].second);
}
cout << heap.size() << endl;
return 0;
}
4.区间覆盖
题目:给定N个闭区间[ai,bi]以及一个线段区间[s,t],请你选择尽量少的区间,将指定线段区间完全覆盖。
输出最少区间数,如果无法完全覆盖则输出-1。
算法思路:
1.先以左端点升序排序;
2.起点为st,找到左端点小于等于st的区间,并从中选择右端点最大的区间,用这个右端点更新st;
3.如果找不到左端点小于st的区间,或者循环完所有区间也无法覆盖所给区间,则返回-1。
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 100010;
pair<int , int> range[N];
bool success;
int st , ed;
int ans;
int n;
int main()
{
cin >>st >> ed >> n;
for(int i = 0 ; i < n ; i++)
cin >> range[i].first >> range[i].second;
sort(range , range + n);
for(int i = 0 ; i < n ; i++)
{
int j = i , r = -2e9;//r用来记录右端点最远的点
while(j < n && range[j].first <= st)
r = max(r , range[j++].second);
if(r < st)
{
ans = -1;
break;
}
ans++;
if(r >= ed)
{
success = true;
break;
}
st = r;
i = j -1;
}
if(!success) ans = -1;
cout << ans << endl;
return 0;
}