区间分组(AcWing)

文章介绍了两种处理区间划分问题的思路,原始思路基于贪心和递减数组,效率低;改进后采用小根堆,简化条件,只需找到一个合法插入位置,提高了算法的效率。
摘要由CSDN通过智能技术生成

题目链接:

https://www.acwing.com/problem/content/908/

思路:(贪心)

(思路一)比较容易想到,先按照区间左端点从小到大排序,然后从前往后依次处理每一个区间。

维护一个数组b,b[i]表示第i组区间的最右端点的值。并且保证数组b是递减的。

那么对于一个新来的区间[l,r],我们二分查询第一个bk满足bk<l(则b(k-1)>=l), 说明第k组是与当前区间没有交点的,因此可以把当前区间插入到第k组中,并且很容易证明,这么做是最优的(i.e. 当前区间最优的插入位置应当是小于l的最大bi处), 然后用r去更新b[k],并调整数组b的顺序,使其保持递减的性质。

若不存在这样的k,那就新开一组,来存放当前区间[l,r]

问题: 上述思路正确性显然是没有问题的,但是无法高效的维护数组b,因为要用r去更新b[k],那么此时就不能保证数组b是递减的,需要将b[k] 往前调,换到一个合适的位置,使得满足数组b是递减的。这一步好像没法高效的完成,复杂度只能是O(n), 从而使得整个算法的复杂度是O(n2),不行。

上面的做法难以维护数组b,本质上是因为数组b的性质太强了(是一个递减的数组),我们可以“退一步”/“弱化条件”,一个相对较弱的数组b,是否可以满足要求? 将当前区间插入的时候,是否一定要插入最优位置? 插入到一个合法但不是最优的位置是否可行? 考虑到这两个问题,就得到了下面这个正解:

(思路二): 

先按照区间左端点从小到大排序,然后从前往后依次处理每一个区间。

维护一个数组b,b[i]表示第i组区间的最右端点的值。

那么对于一个新来的区间[l,r],我们找一个bk满足bk<l, 说明第k组是与当前区间没有交点的,因此可以把当前区间插入到第k组中(若有多个这样的k存在,则任取一个)

若不存在这样的k,那就新开一组,来存放当前区间[l,r]

思路二的证明: 假设最优解为ans, 我们的解法求得的答案为cnt.

(1) ans <= cnt. 因为ans是最优解,而cnt是一个合法解,故ans <= cnt

(2) ans >= cnt. 考虑第cnt组第一次被构造出来,此时当前区间为[l,r], 则他与前面的cnt-1组一定都有交点(否则,他一定可以插入某一个组),根据我们的算法,也就是说: l <= bi (1=<i<=cnt-1)

我们考察每一个1=<i<=cnt-1, bi是该组区间中的最右端点,bi一定是由该组区间中的某一个区间产生的,i.e. 第i组一定存在一个区间[ai,bi],则点l是当前区间[l,r] 和 区间[ai,bi] 的公共点。

注意到,上面的结论对任意1=<i<=cnt-1都成立,也就是说: 我们找到了cnt个有公共点的区间:[a1,b1], [a2,b2], ... [a(cnt-1),b(cnt-1)] 和[l,r]. (他们存在公共点l)

则这cnt个区间,任意两个区间都不能位于同一组(因为他们有公共点),故至少要cnt组,从而得到了ans >= cnt.

结合(1),(2), 得到: ans=cnt. 故思路二是正解。

tips: 由思路二的证明可以看到,实际上根本不需要数组b是有序的,也不需要当前区间插入到一个最优的位置,我们从思路一到思路二的转变,有点“返璞归真”的感觉,是去掉了一些“冗余条件”,在保证正确性的前提下,使得信息的维护更加方便了。

警示: 以后想到了一种正确的做法,但是维护不出来的时候,可以考虑“弱化”一些条件,看看是不是弱化条件后的算法还是正确的!(返璞归真法)

代码实现:

由于思路二只需要找到一个合法的k,满足bk<l即可,那么可以直接维护一个小根堆,单次查找最小值的复杂度为log(n).

代码:

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<queue>
using namespace std;
const int N=1e5+10;
struct node{
	int l;
	int r;
	bool friend operator<(node x,node y){
		if(x.l!=y.l) return x.l < y.l;
		else return x.r < y.r;
	}
};
node a[N];
int n;
int cnt=0;
bool cmp(int x,int y){
	return x > y;
}
priority_queue<int,vector<int>,greater<int> >q;

int main(void){
	
	scanf("%d",&n);
	for(int i=1;i<=n;i++) scanf("%d%d",&a[i].l,&a[i].r);
	sort(a+1,a+1+n);
	q.push(a[1].r);
	for(int i=2;i<=n;i++){
		int x=q.top();
		q.pop();
		if(x<a[i].l){
			x=a[i].r;
		}
		else{
			q.push(a[i].r);
		}
		q.push(x);
	}
	int ans=q.size();
	printf("%d",ans);
	return 0;
}

  • 22
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值