贪心 - 区间问题(区间选点 + 最大不相交区间数量 + 区间分组 + 区间覆盖)
1、区间选点
给定N个闭区间[ai,bi],请你在数轴上选择尽量少的点,使得每个区间内至少包含一个选出的点。
输出选择的点的最小数量。
位于区间端点上的点也算作区间内。
输入格式
第一行包含整数N,表示区间数。
接下来N行,每行包含两个整数ai,bi,表示一个区间的两个端点。
输出格式
输出一个整数,表示所需的点的最小数量。
数据范围
1≤N≤105,
−109≤ai≤bi≤109
输入样例:
3
-1 1
2 4
3 5
输出样例:
2
思路:
将 给 定 区 间 按 右 端 点 从 小 到 大 排 序 。 将给定区间按右端点从小到大排序。 将给定区间按右端点从小到大排序。
每 次 判 断 当 前 区 间 的 右 端 点 r i 能 否 覆 盖 到 后 面 的 区 间 , 即 判 断 r i > = l i + k , k = 1 , 2 , . . . 。 每次判断当前区间的右端点r_i能否覆盖到后面的区间,即判断r_i>=l_{i+k},k=1,2,...。 每次判断当前区间的右端点ri能否覆盖到后面的区间,即判断ri>=li+k,k=1,2,...。
若 能 , 则 将 这 k 个 区 间 加 入 到 同 一 集 合 , 表 示 这 些 集 合 中 的 区 间 均 包 含 点 r i 。 若 不 能 , 则 将 r i 更 新 为 r i + 1 , 同 时 答 案 需 要 增 加 1 。 再 看 r i + 1 最 多 能 够 覆 盖 多 少 个 区 间 。 若能,则将这k个区间加入到同一集合,表示这些集合中的区间均包含点r_i。\\若不能,则将r_i更新为r_{i+1},同时答案需要增加1。再看r_{i+1}最多能够覆盖多少个区间。 若能,则将这k个区间加入到同一集合,表示这些集合中的区间均包含点ri。若不能,则将ri更新为ri+1,同时答案需要增加1。再看ri+1最多能够覆盖多少个区间。
目 标 就 是 选 尽 量 少 的 点 , 能 够 使 得 涵 盖 了 所 有 区 间 。 按 右 端 点 排 序 后 , 因 为 每 个 区 间 都 要 被 分 组 , 那 么 选 择 右 端 点 就 能 够 涵 盖 到 更 多 的 区 间 , 使 得 分 的 组 数 最 小 。 目标就是选尽量少的点,能够使得涵盖了所有区间。\\按右端点排序后,因为每个区间都要被分组,那么选择右端点就能够涵盖到更多的区间,使得分的组数最小。 目标就是选尽量少的点,能够使得涵盖了所有区间。按右端点排序后,因为每个区间都要被分组,那么选择右端点就能够涵盖到更多的区间,使得分的组数最小。
代码:
#include<iostream>
#include<cstdio>
#include<algorithm>
#define P pair<int,int>
#define r first
#define l second
using namespace std;
const int N=1e5+10;
int n;
P seg[N];
int main()
{
scanf("%d",&n);
for(int i=0;i<n;i++) scanf("%d%d",&seg[i].l,&seg[i].r);
sort(seg,seg+n);
int res=1,t=seg[0].r;
for(int i=1;i<n;i++)
if(t<seg[i].l)
{
t=seg[i].r;
res++;
}
cout<<res<<endl;
return 0;
}
2、最大不相交区间数量
给定N个闭区间[ai,bi],请你在数轴上选择若干区间,使得选中的区间之间互不相交(包括端点)。
输出可选取区间的最大数量。
输入格式
第一行包含整数N,表示区间数。
接下来N行,每行包含两个整数ai,bi,表示一个区间的两个端点。
输出格式
输出一个整数,表示可选取区间的最大数量。
数据范围
1≤N≤105,
−109≤ai≤bi≤109
输入样例:
3
-1 1
2 4
3 5
输出样例:
2
思路:
将 给 定 区 间 按 右 端 点 从 小 到 大 排 序 。 将给定区间按右端点从小到大排序。 将给定区间按右端点从小到大排序。
每 次 判 断 当 前 区 间 的 右 端 点 r i 与 后 面 的 区 间 是 否 有 交 集 , 即 判 断 r i > = l i + k , k = 1 , 2 , . . . 。 每次判断当前区间的右端点r_i与后面的区间是否有交集,即判断r_i>=l_{i+k},k=1,2,...。 每次判断当前区间的右端点ri与后面的区间是否有交集,即判断ri>=li+k,k=1,2,...。
若
能
,
说
明
两
个
区
间
有
交
集
,
则
答
案
需
要
增
加
1
,
同
时
再
看
r
i
+
1
,
做
同
样
判
断
。
若能,说明两个区间有交集,则答案需要增加1,同时再看r_{i+1},做同样判断。
若能,说明两个区间有交集,则答案需要增加1,同时再看ri+1,做同样判断。
若
不
能
,
说
明
两
个
区
间
无
交
集
,
则
将
这
k
个
区
间
加
入
到
集
合
中
,
表
示
集
合
中
的
区
间
没
有
交
集
。
若不能,说明两个区间无交集,则将这k个区间加入到集合中,表示集合中的区间没有交集。
若不能,说明两个区间无交集,则将这k个区间加入到集合中,表示集合中的区间没有交集。
代 码 同 第 一 题 完 全 相 同 。 代码同第一题完全相同。 代码同第一题完全相同。
代码:
#include<iostream>
#include<cstdio>
#include<algorithm>
#define P pair<int,int>
#define r first
#define l second
using namespace std;
const int N=1e5+10;
int n;
P seg[N];
int main()
{
scanf("%d",&n);
for(int i=0;i<n;i++) scanf("%d%d",&seg[i].l,&seg[i].r);
sort(seg,seg+n);
int res=1,t=seg[0].r;
for(int i=1;i<n;i++)
if(t<seg[i].l)
{
t=seg[i].r;
res++;
}
cout<<res<<endl;
return 0;
}
3、区间分组
给定N个闭区间[ai,bi],请你将这些区间分成若干组,使得每组内部的区间两两之间(包括端点)没有交集,并使得组数尽可能小。
输出最小组数。
输入格式
第一行包含整数N,表示区间数。
接下来N行,每行包含两个整数ai,bi,表示一个区间的两个端点。
输出格式
输出一个整数,表示最小组数。
数据范围
1≤N≤105,
−109≤ai≤bi≤109
输入样例:
3
-1 1
2 4
3 5
输出样例:
2
思路:
先 将 给 定 区 间 按 左 端 点 从 小 到 大 排 序 。 先将给定区间按左端点从小到大排序。 先将给定区间按左端点从小到大排序。
对 于 每 个 给 定 区 间 , 每 次 判 断 当 前 区 间 能 否 加 入 到 已 经 分 好 的 组 中 去 。 即 判 断 已 经 分 好 的 组 中 , 每 个 组 所 有 区 间 中 的 最 右 边 的 端 点 m a x _ r 与 当 前 区 间 的 左 端 点 l i 的 大 小 关 系 。 对于每个给定区间,每次判断当前区间能否加入到已经分好的组中去。\\即判断已经分好的组中,每个组所有区间中的最右边的端点max\_r与当前区间的左端点l_i的大小关系。 对于每个给定区间,每次判断当前区间能否加入到已经分好的组中去。即判断已经分好的组中,每个组所有区间中的最右边的端点max_r与当前区间的左端点li的大小关系。
若
m
a
x
_
r
<
l
i
,
说
明
当
前
区
间
不
能
加
入
到
该
组
,
反
之
说
明
可
以
加
入
到
该
组
。
若max\_r<l_i,说明当前区间不能加入到该组,反之说明可以加入到该组。
若max_r<li,说明当前区间不能加入到该组,反之说明可以加入到该组。
若
已
经
分
好
的
组
中
,
没
有
任
何
一
组
的
m
a
x
_
r
<
l
i
,
则
需
重
新
开
一
个
组
将
当
前
区
间
加
入
进
去
。
若已经分好的组中,没有任何一组的max\_r<l_i,则需重新开一个组将当前区间加入进去。
若已经分好的组中,没有任何一组的max_r<li,则需重新开一个组将当前区间加入进去。
可
见
整
个
过
程
需
要
频
繁
地
查
询
和
修
改
某
一
组
的
m
a
x
_
r
,
这
个
过
程
可
以
通
过
小
根
堆
来
维
护
。
可见整个过程需要频繁地查询和修改某一组的max\_r,这个过程可以通过小根堆来维护。
可见整个过程需要频繁地查询和修改某一组的max_r,这个过程可以通过小根堆来维护。
用
一
个
小
根
堆
来
存
储
各
组
的
最
右
端
点
。
用一个小根堆来存储各组的最右端点。
用一个小根堆来存储各组的最右端点。
① 、 若 当 前 区 间 的 左 端 点 l i 小 于 等 于 堆 顶 元 素 , 说 明 需 要 重 新 开 一 个 组 将 当 前 区 间 加 入 进 去 , 同 时 需 要 加 入 该 区 间 的 右 端 点 到 堆 。 ①、若当前区间的左端点l_i小于等于堆顶元素,说明需要重新开一个组将当前区间加入进去,\\\qquad同时需要加入该区间的右端点到堆。 ①、若当前区间的左端点li小于等于堆顶元素,说明需要重新开一个组将当前区间加入进去,同时需要加入该区间的右端点到堆。
② 、 若 当 前 区 间 的 左 端 点 大 于 堆 顶 元 素 , 说 明 可 以 将 该 区 间 加 入 到 这 个 组 , 同 时 这 个 组 的 最 右 端 点 需 要 更 新 为 当 前 区 间 的 右 端 点 。 ②、若当前区间的左端点大于堆顶元素,说明可以将该区间加入到这个组,\\\qquad同时这个组的最右端点需要更新为当前区间的右端点。 ②、若当前区间的左端点大于堆顶元素,说明可以将该区间加入到这个组,同时这个组的最右端点需要更新为当前区间的右端点。
最 终 , 堆 中 元 素 的 个 数 就 是 分 组 的 个 数 。 最终,堆中元素的个数就是分组的个数。 最终,堆中元素的个数就是分组的个数。
代码:
#include<cstdio>
#include<algorithm>
#include<queue>
#define P pair<int,int>
#define l first
#define r second
using namespace std;
const int N=1e5+10;
int n;
P seg[N];
int main()
{
scanf("%d",&n);
for(int i=0;i<n;i++) scanf("%d%d",&seg[i].l,&seg[i].r);
sort(seg,seg+n);
priority_queue<int,vector<int>,greater<int>> heap;
heap.push(seg[0].r);
for(int i=1;i<n;i++)
{
int t=heap.top();
if(seg[i].l<=t) heap.push(seg[i].r);
else
{
heap.pop();
heap.push(seg[i].r);
}
}
printf("%d\n",heap.size());
return 0;
}
4、区间覆盖
给定N个闭区间[ai,bi]以及一个线段区间[s,t],请你选择尽量少的区间,将指定线段区间完全覆盖。
输出最少区间数,如果无法完全覆盖则输出-1。
输入格式
第一行包含两个整数s和t,表示给定线段区间的两个端点。
第二行包含整数N,表示给定区间数。
接下来N行,每行包含两个整数ai,bi,表示一个区间的两个端点。
输出格式
输出一个整数,表示所需最少区间数。
如果无解,则输出-1。
数据范围
1≤N≤105,
−109≤ai≤bi≤109,
−109≤s≤t≤109
输入样例:
1 5
3
-1 3
2 4
3 5
输出样例:
2
思路:
首 先 将 给 定 区 间 按 左 端 点 从 小 到 大 排 序 。 首先将给定区间按左端点从小到大排序。 首先将给定区间按左端点从小到大排序。
对 于 所 有 能 够 覆 盖 起 点 s t 的 区 间 , 我 们 选 择 其 中 右 端 点 l j 最 大 的 一 个 区 间 , 接 着 将 s t 更 新 为 l j 。 对于所有能够覆盖起点st的区间,我们选择其中右端点l_j最大的一个区间,接着将st更新为l_j。 对于所有能够覆盖起点st的区间,我们选择其中右端点lj最大的一个区间,接着将st更新为lj。
初 始 化 起 点 s t = s , i 从 前 到 后 遍 历 每 个 区 间 , 用 双 指 针 j 来 寻 找 能 够 覆 盖 s t 的 区 间 的 右 端 点 的 最 大 值 r 。 初始化起点st=s,i从前到后遍历每个区间,用双指针j来寻找能够覆盖st的区间的右端点的最大值r。 初始化起点st=s,i从前到后遍历每个区间,用双指针j来寻找能够覆盖st的区间的右端点的最大值r。
① 、 若 没 有 任 何 区 间 能 够 覆 盖 s t , 可 提 前 退 出 循 环 , 输 出 − 1 。 ①、若没有任何区间能够覆盖st,可提前退出循环,输出-1。 ①、若没有任何区间能够覆盖st,可提前退出循环,输出−1。
② 、 若 已 经 覆 盖 了 终 点 e , 可 提 前 退 出 , 输 出 所 需 的 区 间 数 量 r e s 。 ②、若已经覆盖了终点e,可提前退出,输出所需的区间数量res。 ②、若已经覆盖了终点e,可提前退出,输出所需的区间数量res。
③ 、 否 则 就 更 新 s t 为 r , i 移 到 r 所 在 区 间 的 后 一 个 区 间 , 重 复 以 上 操 作 。 ③、否则就更新st为r,i移到r所在区间的后一个区间,重复以上操作。 ③、否则就更新st为r,i移到r所在区间的后一个区间,重复以上操作。
代码:
#include<iostream>
#include<cstdio>
#include<algorithm>
#define P pair<int,int>
#define l first
#define r second
using namespace std;
const int N=1e5+10;
int n,s,e;
P seg[N];
int main()
{
scanf("%d%d%d",&s,&e,&n);
for(int i=0;i<n;i++) scanf("%d%d",&seg[i].l,&seg[i].r);
sort(seg,seg+n);
int st=s,res=0;
bool flag=false;
for(int i=0;i<n;i++)
{
int j=i,r=-2e9;
while(j<n && seg[j].l<=st)
{
r=max(r,seg[j].r);
j++;
}
if(r<st)
{
res=-1;
break;
}
res++;
if(r>=e)
{
flag=true;
break;
}
st=r;
i=j-1;
}
if(!flag) res=-1;
cout<<res<<endl;
return 0;
}