Acwing 905.区间选点
实现思路:
- 将每个区间按照右端点从小到大排序
- 从前往后依次枚举每个区间
- 若当前区间中已经包含点,则跳过;
- 否则(即当前区间的左端点大于该点),选择当前区间的右端点;
- 证明:比较最终结果ans和选出的点个数cnt大小关系,即证ans>=cnt&&cnt>=ans
- 先证ans<=cnt:由于上述方法选择的方案保证了每一个区间都至少包含一个点,因此为一个合法的方案,而ans表示的是合法方案中的最少点个数,因此ans<=cnt。
- 再证ans>=cnt:考虑没有被跳过的区间,区间互不相交,因此选中cnt个区间,要想覆盖所有区间,最终的答案一定至少为cnt个点(因为区间是独立的),即ans>=cnt。得证。
具体实现代码(详解版)
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 100010;
int n;
struct Range{
int l, r; // l 表示区间左端点, r 表示区间右端点
// 重载小于运算符,用于将区间按照右端点 r 进行升序排序
bool operator< (const Range &W) const
{
return r < W.r; // 按右端点 r 从小到大排序
}
} range[N]; // 定义一个大小为 N 的 Range 数组来存储区间
//自定义排序函数
// bool cmp(Range r1,Range r2){
// return r1.r < r2.r;
// }
int main() {
cin >> n;
for (int i = 0; i < n; i++) {
int l, r;
cin >> l >> r;
range[i] = {l, r}; // 将左端点和右端点存入 range 数组
}
// 对区间按照右端点进行排序
sort(range, range + n);
//sort(range,range + n,cmp);
int res = 0; // 结果变量,记录需要选择的点的数量
int ed = -2e9; // 初始化已选点的右端点,设为一个很小的值,确保第一个区间被选中
// 遍历所有区间
for (int i = 0; i < n; i++) {
// 如果当前区间的左端点大于上一个选中的点的右端点
if (range[i].l > ed) {
res++; // 需要选择一个新的点
ed = range[i].r; // 更新已选点的右端点为当前区间的右端点
}
}
cout << res << endl;
return 0;
}
注意:排序也可以自己写一个cmp函数,不用重载小于运算符(感觉不好记hhh)!
Acwing 908.最大不相交区间数量
实现思路:与上题思路一样,代码也一样
证明:比较最终结果ans和选出的区间个数cnt大小关系,即证ans>=cnt&&cnt>=ans。
先证ans>=cnt:由于选出的区间各不相交,因此为合法的方案,而ans为最大的一个,所以有ans>=cnt成立;
再证ans<=cnt:cnt表示每个区间都至少有一个选好的点,而ans表示表示所有不相交的区间的数量,说明至少应该有ans个点才能使得每一个区间都有一个点,所以ans<=cnt成立。
具体实现代码:
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 100010;
int n;
struct Range{
int l, r; // l 表示区间左端点, r 表示区间右端点
// 重载小于运算符,用于将区间按照右端点 r 进行升序排序
bool operator< (const Range &W) const
{
return r < W.r; // 按右端点 r 从小到大排序
}
} range[N]; // 定义一个大小为 N 的 Range 数组来存储区间
//自定义排序函数
// bool cmp(Range r1,Range r2){
// return r1.r < r2.r;
// }
int main() {
cin >> n;
for (int i = 0; i < n; i++) {
int l, r;
cin >> l >> r;
range[i] = {l, r}; // 将左端点和右端点存入 range 数组
}
// 对区间按照右端点进行排序
sort(range, range + n);
//sort(range,range + n,cmp);
int res = 0; // 结果变量,记录需要选择的点的数量
int ed = -2e9; // 初始化已选点的右端点,设为一个很小的值,确保第一个区间被选中
// 遍历所有区间
for (int i = 0; i < n; i++) {
// 如果当前区间的左端点大于上一个选中的点的右端点
if (range[i].l > ed) {
res++; // 需要选择一个新的区间
ed = range[i].r; // 更新已选点的右端点为当前区间的右端点
}
}
cout << res << endl;
return 0;
}
Acwing 906.区间分组
实现思路:
- 将所有区间按照左端点从小到大排序;
- 从前往后处理每个区间
- 判断能否将当前区间放到某个现有的组中,判断现有的组中的最后一个区间的右端点(即最大右端点),是否小于当前区间的左端点
- 若小于,则意味着该组存在一个区间与当前区间相交,则不能放到该组,需要重新开一个组;
- 否则可以加入当前组(没有相交)
- 判断能否将当前区间放到某个现有的组中,判断现有的组中的最后一个区间的右端点(即最大右端点),是否小于当前区间的左端点
- 使用小根堆来存储所有组的右端点,那么堆顶就是右端点最小的一个组,如果当前的左端点小于这个组的右端点,就表示当前区间与现有组产生相交,必然要开一个新的组即加入堆中。否则当前区间至少可以加入堆顶的那个组,更新一下右端点(就是删除堆顶元素,再添加)
- 最后输出堆的大小即为最小组数。
具体实现代码(详解版):
#include <iostream>
#include <algorithm>
#include <queue>
using namespace std;
const int N = 100010;
int n;
struct Range{
int l, r; // l 表示区间左端点, r 表示区间右端点
// 重载小于运算符,用于将区间按照右端点 r 进行升序排序
bool operator< (const Range &W) const
{
return l < W.l; // 按右端点 r 从小到大排序
}
} range[N]; // 定义一个大小为 N 的 Range 数组来存储区间
//自定义排序函数
// bool cmp(Range r1,Range r2){
// return r1.r < r2.r;
// }
int main() {
cin >> n;
for (int i = 0; i < n; i++) {
int l, r;
cin >> l >> r;
range[i] = {l, r}; // 将左端点和右端点存入 range 数组
}
// 对区间按照左端点进行排序
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].l) heap.push(range[i].r);//新开一个组
else{//否则可以加入堆顶的组,更新一下右端点
heap.pop();
heap.push(range[i].r);
}
}
cout << heap.size() << endl;
return 0;
}
Acwing 907.区间覆盖
实现思路:
设线段的左端点为start
,右端点为end
- 所有区间按照左端点从小到大排序
- 从前往后依次枚举每个区间,在所有能覆盖
start
的区间中,选择一个右端点最大的区间,随后,将start
更新为选中区间的右端点。当start >= end
,结束 - 用双指针算法来找左端点<start,且右端点最大的区间,若找到的右端点依旧小于start,即无解;否则区间数量+1,且更新start
- 注意:一轮过后i=j-1,
j
是满足条件的区间,为了避免一些不必要的i
枚举,所以i
可以跳到满足条件的区间继续向后,但因为一轮后i++
,所以先-1
,下一轮就从j
开始,这样又不会缺少或跳过满足的区间
具体实现代码(详解版):
#include <iostream>
#include <algorithm>
#include <queue>
using namespace std;
const int N = 100010;
int n;
struct Range {
int l, r; // l 表示区间左端点, r 表示区间右端点
// 重载小于运算符,用于将区间按照左端点 l 进行升序排序
bool operator< (const Range &W) const {
return l < W.l; // 按左端点 l 从小到大排序
}
} range[N]; // 定义一个大小为 N 的 Range 数组来存储区间
int main() {
int start, end;
cin >> start >> end;
cin >> n;
for (int i = 0; i < n; i++) {
int l, r;
cin >> l >> r;
range[i] = {l, r}; // 将左端点和右端点存入 range 数组
}
// 对区间按照左端点 l 进行升序排序
sort(range, range + n);
int res = 0; // 结果变量,记录需要选择的区间数量
bool success = false; // 表示是否能够找到满足条件的解
// 遍历每个区间
for (int i = 0; i < n; i++) {
int j = i, r = -2e9; // r 用来记录当前覆盖的最大右端点
// 寻找所有能够与当前 start 相交的区间
while (j < n && range[j].l <= start) {
r = max(r, range[j].r); // 更新能覆盖的最远右端点
j++; // 继续往后找
}
// 如果找不到一个右端点能够覆盖当前 start,说明无解
if (r < start) break;
// 找到一个合适的区间,区间数量加 1
res++;
// 如果右端点已经能够覆盖到 end,说明找到了解
if (r >= end) {
success = true;
break;
}
// 更新新的起点为当前找到的最远右端点,继续寻找下一个区间
start = r;
i = j - 1; // 更新 i 的位置
}
cout << (success ? res : -1) << endl;
return 0;
}
以上就是几个经典的区间问题,重点在于思路的证明(不行就试)