这道题目让我又重新认识了一下离散化:
首先总结一下离散化的特点:
1)有时区间的端点并不是整数,或者区间太大导致建树内存开销过大而MLE,那么就需要进行离散化后再建树。
2)意思是将区间范围很大的数据集映射到较小的数据集,这样建树更加有效,或者说我们只取需要的值来用。
这个意思说到底就是进行映射,把原来很大的映射到一个较小的空间中去。
题意:
给定一些海报,它们可能相互重叠,告诉你每个海报的宽度(它们的高度都是一样的)和先后的叠放次序,问没有被完全盖住的海报有多少张?
这里我们注意到了数据的范围:海报最多100000张,而墙的范围最大是10的7次。所以我们肯定不能以墙来建树。(而且这里海报都是完全覆盖每一个墙面的)
所以我们要把海报的左右端点进行离散化,也就是把原先很大的每个端点进行标号,把它转化为更小的数据
这里要注意一点就是:当它们之间距离等于1的时候,只要+1就可以了,但是当距离大于1的时候,那么要+2
原因:(来源于http://blog.csdn.net/non_cease/article/details/7383736)
解法:离散化,如下面的例子(题目的样例),因为单位1是一个单位长度,将下面的
1 2 3 4 6 7 8 10
— — — — — — — —
1 2 3 4 5 6 7 8
离散化 X[1] = 1; X[2] = 2; X[3] = 3; X[4] = 4; X[5] = 6; X[7] = 8; X[8] = 10
于是将一个很大的区间映射到一个较小的区间之中了,然后再对每一张海报依次更新在宽度为1~8的墙上(用线段树),最后统计不同颜色的段数。
但是只是这样简单的离散化是错误的,
如三张海报为:1~10 1~4 6~10
离散化时 X[ 1 ] = 1, X[ 2 ] = 4, X[ 3 ] = 6, X[ 4 ] = 10
第一张海报时:墙的1~4被染为1;
第二张海报时:墙的1~2被染为2,3~4仍为1;
第三张海报时:墙的3~4被染为3,1~2仍为2。
最终,第一张海报就显示被完全覆盖了,于是输出2,但实际上明显不是这样,正确输出为3。
新的离散方法为:在相差大于1的数间加一个数,例如在上面1 4 6 10中间加5(算法中实际上1,4之间,6,10之间都新增了数的)
X[ 1 ] = 1, X[ 2 ] = 4, X[ 3 ] = 5, X[ 4 ] = 6, X[ 5 ] = 10
这样之后,第一次是1~5被染成1;第二次1~2被染成2;第三次4~5被染成3
最终,1~2为2,3为1,4~5为3,于是输出正确结果3。
接下来就是使用线段树来查询已经标号好的区间了。
这里我们进行从后往前查找,这样前面贴上去的海报就不会被覆盖掉了。
插入一张海报时,如果发现它对应的瓷砖有一部分露出来,那么就说明该海报时可见的。
#include<stdio.h>
#include<string.h>
#include<iostream>
#include<algorithm>
#include<vector>
#include<set>
#include<map>
#include<queue>
#include<math.h>
using namespace std;
#define maxn 10010
int l[maxn],r[maxn],x[maxn*2];
int hash[10000100];
struct node{
int l,r;
bool covered;
}tree[1000100];
void build(int v,int l,int r){
tree[v].l=l;
tree[v].r=r;
tree[v].covered=false;
if(l==r) return ;
int mid=(tree[v].l+tree[v].r)>>1;
int temp=v<<1;
build(temp,l,mid);
build(temp+1,mid+1,r);
}
bool query(int v,int l,int r){
//如果v区间直接被覆盖的话,那么直接false,因为前面贴的是不可见的
if(tree[v].covered) return false;
bool ff;
if(tree[v].l==l&&tree[v].r==r){
tree[v].covered=true;
return true;
}
int mid=(tree[v].l+tree[v].r)>>1;
int temp=v<<1;
if(r<=mid) ff=query(temp,l,r);
else if(l>mid) ff=query(temp+1,l,r);
else{
bool f1=query(temp,l,mid);
bool f2=query(temp+1,mid+1,r);
ff=f1||f2; //只需要部分可见就可以了
}
//当左右子节点都被覆盖时,父节点说明也已经是覆盖的了
if(tree[temp].covered&&tree[temp+1].covered){
tree[v].covered=true;
}
return ff;
}
int main(){
int T;
scanf("%d",&T);
while(T--){
int n;
scanf("%d",&n);
memset(l,0,sizeof(l));
memset(r,0,sizeof(r));
memset(x,0,sizeof(x));
memset(hash,0,sizeof(hash));
int count=0;
for(int i=1;i<=n;i++){
scanf("%d%d",&l[i],&r[i]);
x[count++]=l[i];
x[count++]=r[i];
}
sort(x,x+count);
count=unique(x,x+count)-x;
int no=0;
//这里相当于是离散化
for(int i=0;i<count;i++){
hash[x[i]]=no;
if(i<count-1){
if(x[i+1]-x[i]==1) no++;
else no+=2;
}
}
build(1,0,no);
int res=0;
for(int i=n;i>=1;i--){
if(query(1,hash[l[i]],hash[r[i]])){
res++;
}
}
printf("%d\n",res);
}
}
对于离散化还是需要多多理解与思考!