给出n个区间,第i个区间表示为\([l_i,r_i]\),询问把这些最少的分组数,使得每组内每个区间相离,\(1 <= N <= 50,000\)。
解
区间问题,首要考虑排序,因为涉及分组,于是得保存组的信息,不妨设\(a_i\)为分的第i组的最后一个区间的右端点
思路一:按左端点排序
现在来考虑第i个区间的决策点,对于能分进的组j,必然是\(a_j<l_j\),第i个区间才能被分进去,但是选哪一个组,按照朴素的思想,必然是选\(a_j\)最小的或者最大的,因为是按左端点排序,后面的区间左端点必然增大,于是前面区间能分进的组,后面的区间也能分进,于是无论选哪一个组都无所谓,如果不能选,必然要新开一个组。
现在的问题是如果能分进一个组,会不会开一个新组让结果更加优秀,此时微扰法就不好证明了,于是采取决策包容,注意到新开一个组,它能够选择的范围只有第i个区间所包含的位置,而不新开一个组,以后新开一个组,与它同等优秀,但是组的最后一个区间的右端点可以是任意一个区间的右端点,于是后者决策包含前者决策,选后者一定不会差,得证。
思路二:按右端点排序
同样考虑第i个区间的决策点,从已经分的组里面考虑,对于第i个区间能被分进的组j,必然\(a_j<l_i\),那么对于后面的区间右端点是递增的,而左端点的位置不确定,因此i可以选的,j也可以选,能接下来的决策造成了影响,不满足无后效性原则。
总上,我们就只要枚举每一个区间,已经每一个可以分进的组,随便选择一个,而不能分进已存在的组,就新开一个组,这样就可以\(O(n^2)\)解决这个问题了。
但是太慢了,考虑优化,按照特殊化简单化问题的思想,我们只要随便选择一个可以分进的组,不妨选个最特殊的\(a_i\)最小的组,而且如果它都不能满足条件,所有已经存在的组都不能满足条件,这样问题就变成快速寻找数列\(\{a_i\}\)的最小值,并且支持修改,st表萎的(不支持动态修改),线段树树状数组可以,只要你愿意写,但是优先队列才是最简单的,因此就可\(O(nlogn)\)解决问题。
顺便给重载\(greater(),less()\)函数的人提个醒,就是\(greater()\)用的是大于号,后者用的是小于号。
参考代码:
#include <iostream>
#include <cstdio>
#include <queue>
#include <vector>
#include <functional>
#include <algorithm>
#define il inline
#define ri register
#define Size 50050
#define intmax 0x7fffffff
using namespace std;
struct inter{
int l,r,id;
il bool operator<(const inter &x)const{
return l<x.l;
}
il bool operator>(const inter &x)const{
return l>x.l;
}
}I[Size],t;int ans[Size];
priority_queue<inter,vector<inter>,greater<inter> >gzy;
il void read(int&);
int main(){int n;read(n);
for(int i(1);i<=n;++i)
read(I[i].l),read(I[i].r),I[i].id=i;
sort(I+1,I+n+1),gzy.push((inter){-intmax,1});
for(int i(1);i<=n;++i){t=gzy.top();
if(t.l<I[i].l)ans[I[i].id]=t.r,t.l=I[i].r,gzy.pop(),gzy.push(t);
else ans[I[i].id]=gzy.size()+1,gzy.push((inter){I[i].r,gzy.size()+1});
}printf("%lu\n",gzy.size());
for(int i(1);i<=n;++i)printf("%d\n",ans[i]);
return 0;
}
il void read(int &x){
x^=x;ri char c;while(c=getchar(),c<'0'||c>'9');
while(c>='0'&&c<='9')x=(x<<1)+(x<<3)+(c^48),c=getchar();
}