笔记---贪心---区间问题

贪心仅注重与当前的行动是否符合最优,与dp不同,不以大局为重
所以只有局部最优解是全局最优解的时候才能使用贪心

通常贪心能够涉及到排序等,通常并不包含很复杂的算法,所以要多试多猜。

AcWing.905.区间选点

给定 N N N 个闭区间 [ a i , b i ] [a_{i},b_{i}] [ai,bi],请你在数轴上选择尽量少的点,使得每个区间内至少包含一个选出的点。

输出选择的点的最小数量。

位于区间端点上的点也算作区间内。

输入格式
第一行包含整数 N N N,表示区间数。
接下来 N N N 行,每行包含两个整数 a i , b i , a_{i},b_{i}, ai,bi表示一个区间的两个端点。

输出格式
输出一个整数,表示所需的点的最小数量。

数据范围
1 ≤ N ≤ 1 0 5 , 1≤N≤10^{5}, 1N105,
− 1 0 9 ≤ a i ≤ b i ≤ 1 0 9 −10^{9}≤a_{i}≤b_{i}≤10^{9} 109aibi109

输入样例:

3
-1 1
2 4
3 5

输出样例:

2

对于这道题
(1)可以先按照区间的右端点进行排序
(2)之后按照排序后的顺序从前往后枚举每个区间,如果当前区间已经被之前选的点覆盖了,那么就不管这个区间,如果没有被覆盖,就选择这个区间的最右端点。

如果使得我们现在的方案选出来的点数为cnt,以答案数为ans,那么就一定满足ans <= cnt,同时我们的方案又可以保证覆盖所有的区间且尽量一个点覆盖多个区间,那么cnt >= ans,有根据夹逼定理,就可以得到cnt就是最优解ans

详细证明过程:AcWIng算法基础课贪心(一)00:15:00

代码:

#include<iostream>
#include<algorithm>
using namespace std;

const int N = 100010;

int n;
struct Range {
	int l, r;	//左右端点
}range[N];

bool com(Range a, Range b) {//排序规则
	return a.r < b.r;
}

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,com);//按照右端点进行排序

	//res是当前选到的点的数量
	//ed是上一个点的下标
	int res = 0, ed = -2e9;

	for(int i = 0;i < n;i++)	//枚举所有区间
		if (range[i].l > ed) {	//如果当前区间的左端点大于上一个点的右端点
			res++;				//则多选一个点
			ed = range[i].r;	//并把ed更新为这个点
		}

	printf("%d",res);
	return 0;
}

AcWing.908.最大不相交区间数量

给定 N N N 个闭区间 [ a i , b i ] [a_{i},b_{i}] [ai,bi],请你在数轴上选择若干区间,使得选中的区间之间互不相交(包括端点)。

输出可选取区间的最大数量。

输入格式
第一行包含整数 N N N,表示区间数。

接下来 N N N 行,每行包含两个整数 a i , b i , a_{i},b_{i}, ai,bi表示一个区间的两个端点。

输出格式
输出一个整数,表示可选取区间的最大数量。

数据范围
1≤N≤105,
−109≤ai≤bi≤109

输入样例:

3
-1 1
2 4
3 5

输出样例:

2

上题中所选的端点数,恰好就是本体所求的不想交区间数,因为有端点的地方就代表了是相交的区间,那么不相交的地方就一定有端点。

详细证明过程:AcWing算法基础课贪心(一)00:31:00

**代码:**此题代码与上题完全一样

AcWing.906.区间分组

给定 N N N 个闭区间 [ a i , b i ] [a_{i},b_{i}] [ai,bi],请你将这些区间分成若干组,使得每组内部的区间两两之间(包括端点)没有交集,并使得组数尽可能小。

输出最小组数。

输入格式
第一行包含整数 N N N,表示区间数。

接下来 N N N 行,每行包含两个整数 a i , b i , a_{i},b_{i}, ai,bi表示一个区间的两个端点。

输出格式
输出一个整数,表示最小组数。

数据范围
1 ≤ N ≤ 1 0 5 , 1≤N≤10^{5}, 1N105,
− 1 0 9 ≤ a i ≤ b i ≤ 1 0 9 −10^{9}≤a_{i}≤b_{i}≤10^{9} 109aibi109

输入样例:

3
-1 1
2 4
3 5

输出样例:

2

1.将所有区间按照左端点从小到大排序。
2.从前往后枚举所有区间,判断这个区间是否能被放到某个组里,即判断这个区间的左端点是否能够大于其他组的右端点的最大值

如果所有分组的右端点的最大值的最小值都比左端点要大,无法把区间放入任意一个组里,那么就要新开一个组
如果所有分组中的右端点中的最大值存在小于于左端点的组,那么就能够放入一个组,那就将其放入组内并同时更新组内最大的右端点值

如果存在多个组可以放入,直接随便放就可以

证明过程AcWing算法基础课贪心(一)00:53:00

此题需要动态维护最小值,故直接使用小根堆即可

代码:

#include<iostream>
#include<queue>
#include<algorithm>
using namespace std;

const int N = 100010;

int n;
struct Range {
	int l, r;
}range[N];

bool com(Range a, Range b) {		//排序规则
	return a.l < b.l;
}

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,com);		//按左端点排序

	//定义小根堆来维护所有组的右端点的最大值
	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();	//堆中的数量就相当于组的数量

	return 0;
}

AcWing.907.区间覆盖

给定 N N N 个闭区间 [ a i , b i ] [a_{i},b_{i}] [ai,bi] 以及一个线段区间 [ s , t ] [s,t] [s,t],请你选择尽量少的区间,将指定线段区间完全覆盖。

输出最少区间数,如果无法完全覆盖则输出 −1

输入格式
第一行包含两个整数 s s s t t t,表示给定线段区间的两个端点。

第二行包含整数 N N N,表示给定区间数。

接下来 N N N 行,每行包含两个整数 a i , b i , a_{i},b_{i}, ai,bi表示一个区间的两个端点。

输出格式
输出一个整数,表示所需最少区间数。

如果无解,则输出 −1

数据范围
1 ≤ N ≤ 1 0 5 , 1≤N≤10^{5}, 1N105,
− 1 0 9 ≤ a i ≤ b i ≤ 1 0 9 , −10^{9}≤a_{i}≤b_{i}≤10^{9}, 109aibi109,
− 1 0 9 ≤ s ≤ t ≤ 1 0 9 −10^{9}≤s≤t≤10^{9} 109st109

输入样例:

1 5
3
-1 3
2 4
3 5

输出样例:

2

1.将所有区间按左端点从小到大排序
2.从前往后枚举所有区间,在所有能够覆盖指定区间的左端点的区间中选择一个右端点最大的区间,之后将下一次搜索的左端点更新成选择的区间的右端点

代码:

#include<iostream>
#include<algorithm>
using namespace std;

const int N = 100010;

int n; 
struct Range {
	int l, r;
}range[N];

bool com(Range a, Range b) {
	return a.l < b.l;
}

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, com);

	int res = 0;//存答案
	bool success = 0;//标记是否有解
	
	for (int i = 0; i < n; i++) {
		int j = i, r = -2e9;	//用r来表示最大值

		//遍历所有左端点在目标区间的左端点的左边的区间
		while (j < n && range[j].l <= st) {
			r = max(r,range[j].r);		//更新取右端点最大值
			j++;
		}

		if (r < st) {	//如果最大的右端点都没有到达目标区间的左端点,那么无解
			break;
		}

		res++;			//选上了一个区间,就加上一个区间
		if (r >= ed) {	//如果最大的右端点已经大于目标区间右端点了
			success = 1;//就直接得到答案
			break;
		}

		st = r;			//更新st为右端点最大值
		i = j - 1;		//把i更新为j-1,下次循环从j继续开始以新的st为标准来找最大右端点
	}

	if (success)cout << res;
	else cout << -1;
	return 0;
}
  • 20
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值