题目链接:
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;
}