poj(2528)——Mayor's posters(线段树+离散化)

这道题目让我又重新认识了一下离散化:

首先总结一下离散化的特点:

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);
	}
}

对于离散化还是需要多多理解与思考!

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值