贪心就感觉是一个目光很短的人看问题,每次都在一个有限的区域内找到最优解,然后再往后看,贪心也没有具体的套路,所以就把看课程的几道题总结一下。
区间选点
- 问题描述
给定 N 个闭区间 [ai,bi],请你在数轴上选择尽量少的点,使得每个区间内至少包含一个选出的点。
输出选择的点的最小数量。
位于区间端点上的点也算作区间内。
- 分析
首先将每个区间按右端点从小到大排序,然后遍历所有区间,每次都很贪心的取区间的右端点,如果下一个区间已经覆盖了前一个区间的右端点,就跳过。
算法的正确性可以通过模拟示例进行感受,贪心好像严格证明很难证明,另外我又那么菜,只能从实验的角度感受算法的正确性。
- 代码
#include<bits/stdc++.h>
using namespace std;
typedef pair<int,int> PII;
const int N = 1e5+10;
vector<PII> q;
int n;
int main()
{
scanf("%d",&n);
while(n--){
int l,r;
scanf("%d%d",&l,&r);
q.push_back({r,l});
}
sort(q.begin(),q.end());
int res = 1;
int t = q[0].first;
for(int i=1;i<q.size();i++){
int a = q[i].first,b = q[i].second;
if(t<=a&&t>=b)continue;
else{
t = a;
res++;
}
}
printf("%d",res);
return 0;
}
最大不想交区间数量
和上题方法一样。
区间分组
- 描述
给定 N 个闭区间 [ai,bi],请你将这些区间分成若干组,使得每组内部的区间两两之间(包括端点)没有交集,并使得组数尽可能小。
输出最小组数。
- 分析
将区间按左端点从小到大排序,然后遍历所有区间,将可能的分组加到一个小根堆中,每次遍历时如果区间的左端点小于等于堆中最小的右端点,则一定会不能加入现有的分组,需要直接加入堆中;反之可以加入现有的分组,同时右端点的最小值应该更新。
- 代码
#include<bits/stdc++.h>
using namespace std;
typedef pair<int,int> PII;
vector<PII> q;
int n;
int main()
{
scanf("%d",&n);
for(int i=0;i<n;i++){
int a,b;
scanf("%d%d",&a,&b);
q.push_back({a,b});
}
sort(q.begin(),q.end());
priority_queue<int,vector<int>,greater<int>>heap;
for(int i=0;i<n;i++){
auto t = q[i];
if(heap.empty()||heap.top()>=t.first)heap.push(t.second);
else{
heap.pop();
heap.push(t.second);
}
}
printf("%d",heap.size());
return 0;
}
区间覆盖
- 描述
给定 N 个闭区间 [ai,bi] 以及一个线段区间 [s,t],请你选择尽量少的区间,将指定线段区间完全覆盖。
输出最少区间数,如果无法完全覆盖则输出 −1。
- 分析
先将区间按左端点排序,然后遍历所有区间,每次取所有能覆盖线段左起始点的区间的最大的右端点,并用该右端点更新线段的起始点。
- 代码
#include<bits/stdc++.h>
using namespace std;
typedef pair<int,int> PII;
vector<PII>q;
int s,t,n;
int main()
{
scanf("%d%d%d",&s,&t,&n);
for(int i=0;i<n;i++){
int a,b;
scanf("%d%d",&a,&b);
q.push_back({a,b});
}
sort(q.begin(),q.end());
int res = 0;
for(int i=0;i<n;i++){
int j = i,st = -2e9;
while(j<n&&q[j].first<=s){
st = max(st,q[j].second);
j++;
}
res++;
s = st;
if(s>=t)break;
}
if(s<t)printf("-1");
else printf("%d",res);
return 0;
}