Week3 C && POJ - 2376 【贪心算法】

题目链接

http://poj.org/problem?id=2376

题目大意

数轴上有 n (1<=n<=25000)个闭区间 [ai, bi],选择尽量少的区间覆盖一条指定线段 [1, t] (1<=t<=1,000,000)。覆盖整点,即(1,2)+(3,4)可以覆盖(1,4)。不可能办到输出-1。

题外话

在这里插入图片描述在这里插入图片描述
在这里插入图片描述以上WA和AC我均不清楚为啥。

清晰的代码结构是非常重要的,不假思索的写代码可能真的会导致自己都不知道自己写的是啥。一段代码写一天,引以为戒。 明明一层循环可以解决问题,作死写了两层,把自己绕晕。代码如下,引以为戒(主函数里那一坨):
#include<iostream>
#include<algorithm>
#include<cstdio>
using namespace std;
struct Tuple{
	int a;
	int b;
	bool operator <(const Tuple &t) const
	{
		if (a != t.a)	return (a < t.a);
		else return (b < t.b); 
	}
	
	bool in(int n)
	{
		return (n >= a && n <= b);
	}
};
int N, T;
Tuple t[105];
int main ()
{
	scanf("%d %d", &N, &T); 
	for(int i=0;i<N;i++)	scanf("%d %d", &t[i].a, &t[i].b);
	sort(t, t+N);
	
	
	int n=0;
	int point=0;
	int p=0;
	
	
	while((p<N) && (t[p].b<1))	p++;   //排除掉无意义的点 
	while(point < T && p < N)  
	{
		point++;
		while((p<N) && !t[p].in(point))	p++;	
		int ppoint = point;
		point = -1;
		while((p<N) && t[p].in(ppoint))
		{
			point = max(t[p].b, point);
			p++;
		}
		if(point == -1)
		{
			printf("-1\n");
			return 0;
		}
		n++;	
	}
	if(point >= T)	printf("%d\n", n);
	else printf("-1");
	return 0;
} 

后来,我觉得理清思路重写一份或许意义更大一点,很快就AC了。

基本思路

我们要选取最少的区间来覆盖这条线段。大体来讲,我们需要在保证覆盖的情况下,选择尽量"长"的区间。

我们不妨从左到右考虑。第一步,我们必然需要找到一个区间包含线段起点,并且这个区间越大越好,即寻找包含起点的右端点最靠后的区间(并不是真正的说这个区间最长)。这样,我们就完成了一部分任务,不妨把这一部分区间和被包含部分线段砍掉。这样,又有了起点,又可以使用刚才的思路。一直这样做,便能找到最少的区间来覆盖线段。

思路并不难,但是代码要怎么写呢。实际上,如果代码写的不够合理,将特别容易忽略若干"特殊"情况。

以下为代码描述,可跳过直接看代码。

n的取值范围之大迫使我们无法接受O(n 2 ^2 2)的复杂度。我们使用结构体Tuple来代表区间,然后对齐按左端点从小到大进行排序。然后,使用整型变量point来记录当前的起点(可以想象成被已经选中的区间覆盖的那部分线段被砍掉了),使用maxb(初值-1)记录包含point的区间的最大的右端点,使用整型n来记录选择区间个数。

然后从前往后遍历所有的区间。对于每一个区间:

  • 如果这个区间的右端点比point还小,那这个区间没啥用,直接pass掉就好。
  • 如果起点包含在这个区间之中,我们把这个区间的右端点和当前的最大右端点maxb进行比较,其中的最大值当做新的maxb。
  • 如果这个区间的左端点都大于point,说明已经找不到可以包含point的区间了(因为我们曾按照左端点对区间进行了排序)。这个时候,如果maxb为-1,说明我们并没能找到包含point的区间,也就不可能完成覆盖。否则,我们就找到了一个包含point区间的最大右端点,选中这个区间。point 赋值为maxb+1,maxb赋值为-1。如果point已经大于T,说明已经完成了覆盖,直接退出循环。否则,我们需要重新访问这个区间,进行这三条分支判断,因为point已经更新。

循环结束后,我们还需判断maxb是否为-1,如果不是,那就再次更新point为maxb+1(注意题意中区间是代表一段连续的整数),选中区间数加1。

最后,我们判断point是否超过T,若超过,输出n;否则,输出-1。

完整代码
#include<iostream>
#include<algorithm>
#include<cstdio>
using namespace std;
struct Tuple{
	int a;
	int b;
	bool operator <(const Tuple &t) const
	{
		return (a < t.a);
	}
};
int N, T;
Tuple t[25005];
int main()
{
	scanf("%d %d", &N, &T);
	for(int i=0;i<N;i++)
	{
		scanf("%d %d", &t[i].a, &t[i].b);
	}
	sort(t, t+N);
	int point = 1;
	int maxb = -1;
	int n=0;
	
	for(int i=0;i<N;i++)
	{
		if(t[i].b < point)	continue;  //整个区间在要覆盖点的左边 
		else if((t[i].b >= point) && (t[i].a <= point))   // 区间包括要覆盖的点 
		{
			maxb = max(maxb, t[i].b);
		}
		else   //整个区间在当前点的右边 
		{
			if(maxb == -1)   //找不到能装下这个点的区间 
			{
				break;
			}
			else
			{
				n++;
				point = maxb+1;
				maxb = -1;
				if(point > T)	break;
				i--;
			}
		} 
	}
	if(maxb != -1)	
	{
		point = maxb+1;
		n++;
	}
	if(point > T)	printf("%d", n);
	else printf("-1");
	return 0;
} 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值