题意:在墙上贴海报,海报可以互相覆盖,问最后可以看见几张海报。
思路:线段树直接做会TLE+MLE,因此需要离散。所谓离散就是将区段进行压缩,但是又不改变区间的位置关系。
方法就是将区段的端点值去掉相同的进行排序,举个例子:
给定4个区间[2,4] [3,6] [8,10] [6,9],覆盖关系就是后者覆盖前者,每个区间染色依次为 1 2 3 4。
现在我们抽取这4个区间的8个端点,2 4 3 6 8 10 6 9
然后删除相同的端点,这里相同的端点为6,则剩下2 4 3 6 8 10 9
对其升序排序,得2 3 4 6 8 9 10
然后建立映射
2 3 4 6 8 9 10
↓ ↓ ↓ ↓ ↓ ↓ ↓
1 2 3 4 5 6 7
那么新的4个区间为 [1,3] [2,4] [5,7] [4,6],覆盖关系没有被改变。新数轴为1到7,即原数轴的长度从9压缩到6,显然构造[1,7]的线段树比构造[1,10]的线段树更省空间,搜索也更快,但是求解的结果却是一致的。
而这题的难点在于每个数字其实表示的是一个单位长度(并非一个点),这样普通的离散化会造成许多错误(包括我以前的代码,poj这题数据奇弱)
给出下面两个简单的例子应该能体现普通离散化的缺陷:
例子一:1-10 1-4 5-10
例子二:1-10 1-4 6-10
普通离散化后都变成了[1,4][1,2][3,4]
线段2覆盖了[1,2],线段3覆盖了[3,4],那么线段1是否被完全覆盖掉了呢?
例子一是完全被覆盖掉了,而例子二没有被覆盖为了解决这种缺陷,我们可以在排序后的数组上加些处理,比如说[1,2,6,10]
如果相邻数字间距大于1的话,在其中加上任意一个数字,比如加成[1,2,3,6,7,10],然后再做线段树就好了。
#include<cstdio> #include<cstring> #include<algorithm> #define M ((L+R)>>1) #define ls (o<<1) #define rs (o<<1|1) #define lson ls,L,M #define rson rs,M+1,R #define MAXN 10005 using namespace std; int Ft[MAXN*2],F[MAXN*3],setv[MAXN*15];//F[]作为最终映射 Ft[]作为临时映射需要处理后放入F[] bool vis[MAXN]; int Li[MAXN],Ri[MAXN],n,T,fn; int yl,yr,ans,v; void pushdown(int o,int L,int R) {if(setv[o]>=0) {setv[ls]=setv[rs]=setv[o];setv[o]=-1;}} void update(int o,int L,int R) { if(yl<=L && R<=yr) {setv[o]=v;return;} pushdown(o,L,R); if(yl<=M) update(lson); if(M<yr) update(rson); } void query(int o,int L,int R) { if(setv[o]>=0) { if(!vis[setv[o]]) vis[setv[o]]=1,++ans; return; } if(L==R) return; if(1<=M) query(lson); if(M<fn-1) query(rson); } int main() { scanf("%d",&T); while(T-- && scanf("%d",&n)) { int nn=0; for(int i=0;i<n;++i) {scanf("%d%d",&Li[i],&Ri[i]);Ft[nn++]=Li[i];Ft[nn++]=Ri[i];} sort(Ft,Ft+nn); F[1]=Ft[0],fn=2; for(int i=1;i<nn;++i) if(Ft[i]!=Ft[i-1]) { if(Ft[i]-Ft[i-1]>1) F[fn++]=Ft[i-1]+1; F[fn++]=Ft[i]; } memset(vis,0,sizeof(vis)); memset(setv,-1,sizeof(setv)); for(v=0;v<n;++v) { yl=lower_bound(F+1,F+fn,Li[v])-F,yr=lower_bound(F+1,F+fn,Ri[v])-F; update(1,1,fn-1); } ans=0; query(1,1,fn-1); printf("%d\n",ans); } return 0; }