POJ 2528 Mayor's posters (线段树&离散化&延迟更新)

刚接触离散化,开始对其百思不得其解,现在回想,顿时开窍,对线段树的离散化才有了初步了解。于是打算写上这么一篇博文,分享自己的理解过程。注:文中的代码并非完全由我原创,是我根据自己的理解对其进行了些修改,如有错误,望指正。

题目链接:点击打开链接


题目大意:
  在一面墙上以一定的先后顺序贴上海报,每张海报有对应的起始及终止区间,求当最后一张海报贴完后,墙上没有被完全遮盖的海报张数。
题目分析:
  个人觉得这个题目比较经典,因为它不但包括了线段树的基本框架,还融入了延迟更新与离散化,考察方面较广。
我们知道线段树在解决区间问题上是比较适用的,而这题的每张海报其实就是一个区间,所以此题用线段树求解是比较方便的。然而这个题的特殊之处是它数据范围(海报的左右端点1<=li<=ri<=10^7)所以直接开一个这么大的线段树数组不但很可能会超内存,在查询时还一定会超时。所以这里需要运用到离散化的思想,将每个端点的值分别数1-n建立映射关系,以适当减小两端点的间隔差
  但是进行普通的映射是存在问题的。如:当三张海报的区间分别是[1-10],[1-4],[7-10]时,在对其端点进行排序后,若建立的映射为:
                 y:  1       4       7       10
                 x:  1       2       3       4
  则当输入[1-10]时线段树中对应的区间[1-4]被标记为0(即它是数组中的第一个数据);输入区间[1-4]时,对应的区间[1-2]被标记为1;输入区间[7-10]时,对应的区间[3-4]。这样一来根据映射,区间[1-10]就被[1-4]和[7-10]所覆盖,而实际上并没有。
  为什么会这样呢?其实,是在进行上述映射时,原来数据(即海报)的区间间隔差被过度减小了。所以为了不改变原有数据间隔的相对性质,我们在间隔差大于1的两相邻数据间再添加一个数(这个数不对应任何数据,只起到分离两相邻区间的作用),则原来的映射关系就变为:
            y:  1           4           7             10
            x:  1    2     3     4     5      6      7

  我们这样做其实是将原来两相邻区间间隔差大于2的缩小至2,而非为1。如原来[1-4],[7-10],对应的区间[1-2],[2-3]的间隔差为3-2=1,而修改后的为5-3=2。

下面是代码:

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;

const int maxn = 10010;
#define lson l, mid, rt<<1
#define rson mid+1, r, rt<<1|1

bool judge[maxn];
struct Post
{
	int li;
	int ri;
}post[maxn];

int Mp[maxn<<3];   //开辟用于映射的数组
int tree[maxn<<4];
int cnt;

void pushdown(int rt)
{
	if(tree[rt]!=-1)
	{
		tree[rt<<1] = tree[rt];
		tree[rt<<1|1] = tree[rt];
		tree[rt] = -1;
	}
}

void updata(int L, int R, int val, int l, int r, int rt)
{
	if(L<=l && r<=R)
	{
		tree[rt] = val;
		return ;
	}
	pushdown(rt);
	
	int mid = (l+r)>>1;

	if(L<=mid)
	   updata(L, R, val, lson);
	if(R>mid)
		updata(L, R, val, rson);
}

void query(int l, int r, int rt)
{
	if(tree[rt]!=-1)
	{
		if(!judge[tree[rt]])
		   cnt++;

		judge[tree[rt]] = true;
		return;
	}
	if(l == r)     //考虑到li == ri情况
		return ;
	int mid = (l+r)>>1;
	query(lson);
	query(rson);
}
//将映射中每张海报的区间端点与海报张贴的顺序匹配
int find(int val, int n)     
{
	int l = 0;
	int r = n;

	while(l<=r)
	{
		int mid = (l+r)>>1;
		if(Mp[mid]==val)
			return mid;
		if(Mp[mid]>val)
			r = mid-1;
		else
			l = mid+1;
	}
	return -1;
}
//建立映射关系
int Mapping(int nn)
{
	sort(Mp, Mp+nn);
	int m=1, i;
	for(i=1; i<nn; i++)
	{
		if(Mp[i]!=Mp[i-1])
			Mp[m++] = Mp[i];
	}
	for(i = m-1; i>0; i--)
	{
		if(Mp[i]>Mp[i-1]+1)
			Mp[m++] = Mp[i-1] +1;
	}
	sort(Mp, Mp+m);
	return m;
}

int main()
{
	int c, n;
	scanf("%d", &c);
	while(c--)
	{
		scanf("%d", &n);
		int i, nn=0;
		for(i=0; i<n; i++)
		{
			scanf("%d%d", &post[i].li, &post[i].ri);
			Mp[nn++]= post[i].li;
			Mp[nn++]=post[i].ri;
		}

		int m = Mapping(nn);
		memset(tree, -1, sizeof(tree));

		for(i=0; i<n; i++)
		{
			int l = find(post[i].li, m-1);
			int r = find(post[i].ri, m-1);
			updata(l, r, i, 0, m-1, 1);
		}
		memset(judge, false, sizeof(judge));
	    cnt = 0;
        query(0, m-1, 1);
		printf("%d\n", cnt);
	}
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值