算法学习笔记(51)——区间问题
区间问题
区间选点
题目描述
给定 \(N\) 个闭区间 \([a_i,b_i]\),请你在数轴上选择尽量少的点,使得每个区间内至少包含一个选出的点。
输出选择的点的最小数量。
位于区间端点上的点也算作区间内。
输入格式
第一行包含整数 \(N\),表示区间数。
接下来 \(N\) 行,每行包含两个整数 \(a_i,b_i\),表示一个区间的两个端点。
输出格式
输出一个整数,表示所需的点的最小数量。
数据范围
\(1 \le N \le 10^5,\)
\(−10^9 \le a_i \le b_i \le 10^9\)
输入样例:
3
-1 1
2 4
3 5
输出样例:
2
算法思路:
- 将每个区间按照右端点从小到大排序
- 从前往后依次枚举每一个区间
- 如果当前区间已经包含点,则直接pass
- 否则选择当前区间的右端点
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 100010;
int n;
struct Range
{
int l, r;
// 重载小于操作符,按照区间右端点排序
bool operator< (const Range &w) const {
return r < w.r;
}
}range[N];
int main()
{
cin >> n;
for (int i = 0; i < n; i ++ ) {
int l, r;
cin >> l >> r;
range[i] = {l, r};
}
sort(range, range + n);
int res = 0, ed = -2e9;
for (int i = 0; i < n; i ++ )
if (range[i].l > ed) {
res ++;
ed = range[i].r;
}
cout << res << endl;
return 0;
}
最大不相交区间数量
题目描述
给定 \(N\) 个闭区间 \([a_i,b_i]\),请你在数轴上选择若干区间,使得选中的区间之间互不相交(包括端点)。
输出可选取区间的最大数量。
输入格式
第一行包含整数 \(N\),表示区间数。
接下来 \(N\) 行,每行包含两个整数 \(a_i,b_i\),表示一个区间的两个端点。
输出格式
输出一个整数,表示可选取区间的最大数量。
数据范围
\(1 \le N \le 105,\)
\(−10^9 \le a_i \le b_i \le 10^9\)
输入样例:
3
-1 1
2 4
3 5
输出样例:
2
与上一题思路及代码完全相同。
区间分组
题目描述
给定 \(N\) 个闭区间 \([a_i,b_i]\),请你将这些区间分成若干组,使得每组内部的区间两两之间(包括端点)没有交集,并使得组数尽可能小。
输出最小组数。
输入格式
第一行包含整数 \(N\),表示区间数。
接下来 \(N\) 行,每行包含两个整数 \(a_i,b_i\),表示一个区间的两个端点。
输出格式
输出一个整数,表示最小组数。
数据范围
\(1 \le N \le 105,\)
\(−10^9 \le a_i \le b_i \le 10^9\)
输入样例:
3
-1 1
2 4
3 5
输出样例:
2
算法思路
- 将所有区间按左端点从小到大排序
- 从前往后处理每个区间
- 判断能否将其放到某个现有的组中(判断当前区间的左端点是否小于某个现有的组的右端点)
- 如果不存在这样的组,则开新组,然后再将其放进去
- 如果存在这样的组,将其放进去,并更新当前组的
Max_r
- 判断能否将其放到某个现有的组中(判断当前区间的左端点是否小于某个现有的组的右端点)
可以利用小根堆来动态维护最小的Max_r
。
#include <iostream>
#include <algorithm>
#include <queue>
using namespace std;
const int N = 100010;
int n;
struct Range
{
int l, r;
bool operator< (const Range &w) const {
return l < w.l;
}
}range[N];
int main()
{
cin >> n;
for (int i = 0 ; i < n; i ++ ) {
int l, r;
cin >> l >> r;
range[i] = {l, r};
}
sort(range, range + n);
priority_queue<int, vector<int>, greater<int>> heap;
for (int i = 0; i < n; i ++ ) {
auto r = range[i];
if (heap.empty() || heap.top() >= r.l) heap.push(r.r);
else {
int t = heap.top();
heap.pop();
heap.push(r.r);
}
}
cout << heap.size() << endl;
return 0;
}
区间覆盖
题目描述
给定 \(N\) 个闭区间 \([a_i,b_i]\) 以及一个线段区间 \([s,t]\),请你选择尽量少的区间,将指定线段区间完全覆盖。
输出最少区间数,如果无法完全覆盖则输出 \(−1\)。
输入格式
第一行包含两个整数 \(s\) 和 \(t\),表示给定线段区间的两个端点。
第二行包含整数 \(N\),表示给定区间数。
接下来 \(N\) 行,每行包含两个整数 \(a_i,b_i\),表示一个区间的两个端点。
输出格式
输出一个整数,表示所需最少区间数。
如果无解,则输出 \(−1\)。
数据范围
\(1 \le N \le 10^5,\)
\(−10^9 \le a_i \le b_i \le 10^9,\)
\(−10^9 \le s \le t \le 10^9\)
输入样例:
1 5
3
-1 3
2 4
3 5
输出样例:
2
算法思路
- 将所有区间按照左端点从小到大排序
- 从前往后依次枚举每个区间,在所有能覆盖
start
的区间中,选择右端点最大的区间,然后将start
更新成右端点的最大值
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 100010;
int n;
struct Range
{
int l, r;
bool operator< (const Range &w) const {
return l < w.l;
}
}range[N];
int main()
{
int st, ed;
cin >> st >> ed;
cin >> n;
for (int i = 0; i < n; i ++ ) {
int l, r;
cin >> l >> r;
range[i] = {l, r};
}
sort(range, range + n);
int res = 0;
bool success = false;
for (int i = 0; i < n; i ++ ) {
int j = i, r = -2e9;
while (j < n && range[j].l <= st) {
r = max(r, range[j].r);
j ++;
}
if (r < st) {
res = -1;
break;
}
res ++;
if (r >= ed) {
success = true;
break;
}
st = r;
i = j - 1;
}
if (!success) res = -1;
cout << res << endl;
return 0;
}