区间贪心

本文详细探讨了三个区间选择问题:最大不相交区间覆盖、区间选点以及区间完全覆盖。对于每个问题,文章提供了策略和解决思路,并通过排序和贪心算法来优化解决方案。还给出了相关例题和代码实现,帮助理解问题本质和算法应用。
摘要由CSDN通过智能技术生成

问题1:最大不相交区间覆盖问题

数轴上有n个开区间(ai, bi)。选择尽量多个区间,使得这些区间两两没有公共点。

策略:

如果x区间完全包含了y区间,那么我们选择y区间

按照右端点bi从小到大排序,从第一个区间开始选

如果目前的区间与上一个区间有相交的部分,则排除掉(排除掉即该区间不再存在,而不z是不选择这样简单舍弃)

简单来说就是:一定选择第一个区间,每次选区间的时候选择和上一个被选上的区间不相交的区间

思路:

首先对于x完全包含了y的情况,如下图所示,小区间为y,大区间为x

 

 那么我们选择y区间,因为选择x或选择y,对答案的贡献都是1,但是选择y,我们可以给后面选择的区间留更多的位置

明确这个问题后,我们对每个区间按照右端点按照从小到大进行排序,一定要选第一个区间。

排序后区间为b1<=b2<=b3......,我们来考虑左端点a之间的大小关系

i. a1>a2 

这张图片非常熟悉,没错,这种情况出现了包含关系;我们可以总结出,对于任何一个bi>b1,如果它的左端点出现了a1>ai,那么就把这个i区间舍去

ii.排除了上述情况后,此时一定有a1<=a2<=a3<=......,如下图所示

我们将区间1划分为两个部分

如果区间1和区间2没有相交的部分,那么没有影响,否则区间1和区间2我们最多选择一个,为什么我们必须选择区间1呢?

首先我们可以发现,区间1的1部分是不会产生任何影响的,在我们选择区间时,它不会和任何的区间相交

这时问题就成了区间1的2部分和区间2选择哪个,显然根据上面的结论,我们应该选择区间1的2部分即选择区间1,将区间2扔掉

于是区间3又成了“区间1”,重复上述过程

注:选择了区间1后,按照策略,我们要把所有和区间1相交的区间排除在外,所以要记录上一个被选择的区间编号

例题:http://codeup.hustoj.com/problem.php?cid=100000584&pid=0

#include <iostream>
#include <cstdio>
#include <cmath>
#include <cstring>
#include <algorithm>
using namespace std;
struct node{
	int l,r;
}a[100010];
bool cmp(node x,node y)
{
	return x.r<y.r;
}
int main()
{
	int n;
	while(cin>>n)
	{
		if(n==0) break;
		int ans=1;
		for(int i=0;i<n;i++) cin>>a[i].l>>a[i].r;
		sort(a,a+n,cmp);
		//for(int i=0;i<n;i++) cout<<a[i].l<<' '<<a[i].r<<endl;
		int last=a[0].r;
		for(int i=1;i<n;i++)
		{
			if(last<=a[i].l)//和上一个选择的区间不相交 
			{
				ans++;
				last=a[i].r;
			}
		}
		cout<<ans<<endl;	
	}
	return 0;
}

第二次编辑时修改:上面代码中last<=a[i].l判断和上一个选择的区间不相交,是因为给的区间是开区间

cmp函数严格应写成

bool cmp(node x,node y)
{
    if(x.l!=y.l) return x.l>y.l;
    else return x.r<y.r;
}

 

问题2:区间选点问题

数轴上有n个闭区间[ai, bi]。取尽量少的点,使得每个区间内都至少有 一个点(不同区间内含的点可以是同一个)

策略:

按照右端点b从小到大排序,如果b相等则按照a排序

如果存在大区间包含小区间的情况,则不必考虑大区间

取最后一个点,即右端点

思路:

我们认为,如果一个区间内有一个被取到的点,那么这个区间就被满足

与上一问题相似,该问题存在区间包含的情况,同理,如果小区间被满足,那么包含它的大区间一定会满足,所以在区间包含的情况下不必考虑大区间

把所有区间按b从小到大排序(b相同时a从大到小排序,此时满足b1<b2<...<bi),则如果出现区间包含的情况,小区间一定排在前面。第一个区间应该取哪一个点呢?此处的贪心策略是:取最后一个点,即右端点

如图所示蓝点,如果我们选择取中间部分的一个点,有两个区间被满足;我们横移蓝点到红点位置,发现原来的两个区间仍然满足,而且后两个区间也满足了,显然,取最后一个点最容易去满足最多的区间

其实这两个问题实质上就是求不相交区间的个数,选择最多不相交的区间其实和选择选择相交区间最小数量的方案数是完全相同的,只是如果要求输出方案时才会有差别。

代码实现只需修改一处即可,因为这个问题是闭区间

if(last<a[i].l)

 

问题3:区间完全覆盖问题

数轴上有n个闭区间[ai, bi],选择尽量少的区间覆盖一条指定线段(也就是个区间)[s,t]。

策略:

预处理n个区间,每个区间应该删掉自己在[s,t]之外的部分,因为这部分不会为覆盖区间有所贡献

按照左端点a做小到大排序,左端点相同则按照右端点从大到小排序,找出包含起点s在内的最长的区间

新的起点设置在bi,忽略区间在bi之前的部分

就是在能连接区间左边的情况下,尽可能地延申右端区间

思路:

先进行一次预处理,把每个区间在[s,t]外的部分切除,因为这些部分毫无意义,它们在覆盖线段时不会帮上忙

显然,预处理后,如果出现区间包含的情况,那么我们应该舍弃小区间

将各区间按照左端点a从小到大排序,如果区间1的起点>s,无解(后面的区间起点更大,更不可能覆盖到),否则就去选择起点>s中尽可能靠左且长度最长的区间

选择此区间后,更新的新的起点应该设置为bi,并且忽略所有区间在bi之前的部分,就像预处理一样。

参考代码

sort(a+1,a+n+1,cmp);
int left=s,right=t;
while(left<=right)
{
    int maxL=0;
    bool flag=0;
    for(int i=1;i<=n;i++)
    {
        if(a[i].l<=left)
        {
            maxL=max(maxL,a[i].r);
            flag=1;
        }
    }
    if(!flag){cout<<"无解!";return 0;}
    cnt++;
    left=maxL+1;
}
cout<<cnt;

 

一些例题

https://www.luogu.com.cn/problem/P1803 最大不相交区间覆盖裸题

https://www.luogu.com.cn/problem/P2434找出包含最少区间的方案:每次枚举区间,如果该区间和上一个被选中的区间有重合,就更新它们两个合成的大区间的右端点,如果该区间和上一个选中的区间不相交,那么说明我们遇到了一个新的大区间,这时输出上一个大区间的左右端点,然后更新

注意:我们最后一次枚举区间时,如果该区间可以被合并,那就被更新,此时我们无法输出包括了这最后一个区间的大区间,所以循环后要再输出一次;如果最后一个区间不能被更新,那么也是同样的道理,最后一个区间的左右端点仅会被记录而不会输出

if(a[i].s<=lastR)
{
	lastR=max(lastR,a[i].t);
} 
else
{
	cout<<lastL<<' '<<lastR<<endl;
	lastL=a[i].s;
	lastR=a[i].t;
}
    

https://www.luogu.com.cn/problem/P2082和上一题类似,上一题是输出各个区间,这个题是输出这些区间的总长度;定义一个长度变量,如果这个区间和上个被选上的区间相交就更新右端点,否则就加上上一个区间(可能是小区间也可能是大区间)的长度;注意最后要加上最后一段区间的长度,原因上个问题中有说

。。。待更

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值