poj2528 Mayor's posters(线段树+离散化)

题目链接:http://poj.org/problem?id=2528

由于数据范围很大,但实际需要用到的节点数并没有那么多;

[1, 1000000]、[2, 1000001]这两个区间在这个问题中和[1, 3]、[2, 4]两个区间其实并没有什么不同,

但是为了维护前一种情况所需要的线段树就是非常巨大的。

因此需要用到离散化,所谓离散化就是在不改变数据相对关系大小的条件下,对数据进行相应的缩小。

这里需要注意的一个问题是离散化后的节点是连续型的还是离散型的。

所谓连续型即 [ i,j ] 代表的是数轴上i->j这一段连续的区域,

而离散型则代表 i 和 j 这两个点,因此相应的线段树处理是有所不同的。

我这里选择了离散型的。所以当离散化的时候就需要考虑一个问题:顺序相邻但位置不一定相邻。

什么意思呢? 举个例子  有一组数据是 (1,10),(1,3),(6,10)

直接离散的话 会变成 (1,4),(1,2),(3,4)。

由于是离散型的可以看出第一张海报是被覆盖住了,然而实际上却不是如此。

因此在离散化的时候 位置相邻则离散后任然位置连续 比如3,4离散后就是1,2

但位置不相邻的离散化也不能位置连续 比如3,5离散后应该是 1 3,这样就可以解决刚刚那个问题了。

#include <iostream>
#include <cstdio>
#include <map>
#include <vector>
#include <algorithm>

using namespace std;

const int MAX_N = 1e5+5;

int seg[MAX_N << 3];

map<int, int> m;
vector<int> vec;

struct ST{
	int l, r;
} st[MAX_N];

// 待查询区间[a,b) 节点编号k 对应区间[l,r) 
bool update( int a, int b, int k, int l, int r ) {
	// 如果待查询区间被一个 已经贴满了海报的区间覆盖了 则直接返回false 
	if ( a >= l && b <= r && seg[k] ) return false;
	// 不相关的区间 返回false 
	if ( a >= r || b <= l ) return false;
	// [a,b)包含了[l,r) 直接标记此区间已被使用 并返回true
	// 因为根据此函数的写法 只有当[a,b) = [l,r)的时候才会到这里
	// 若seg[k] = 0 则在第一行判断就已经退出了  
	if ( a <= l && b >= r ) {
		seg[k] = 1;
		return true;
	} else {
		int mid = (l+r)>>1;
		int r1, r2, r3, r4;
		if ( mid >= b ) {
			r1 = update( a, b, 2*k+1, l, mid );
		} else if ( mid <= a ) {
			r2 = update( a, b, 2*k+2, mid, r );
 		} else {
 			r3 = update( a, mid, 2*k+1, l, mid );
			r4 = update( mid, b, 2*k+2, mid, r );
 		}
		// 当两个子节点都铺满时 当前节点才算铺满 
		seg[k] = seg[2*k+1] && seg[2*k+2]; 
		
		if ( mid >= b ) {
			return r1;
		} else if ( mid <= a ) {
			return r2;
 		} else {
			return r3 || r4;
 		}
	}
}

int main()
{
	int N, M;
	scanf( "%d%d", &N, &M );
	for ( int i = 0 ; i < N ; ++ i ) {
		scanf( "%d%d", &st[i].l, &st[i].r );
		vec.push_back(st[i].l);
		vec.push_back(st[i].r);
	}
	
	// 离散化
	sort( vec.begin(), vec.end() );
	vec.erase( unique(vec.begin(), vec.end()), vec.end() );
	// 离散化时 若顺序相邻而位置不相邻 则需有一个空位出现
	// 例如 1 3 6 7 10  离散化 后应该是 1 3 5 6 8
	// 防止出现 例如: 1-10  1-3  6-10  若无间隔 则结果是2  其实是3 
	int prev = *vec.begin()-1, lim = 0;
	for ( int i = 0 ; i < vec.size() ; ++ i ) {
		if ( vec[i] == prev+1 ) {
			lim ++;
		} else {
			lim += 2;
		}
		prev = vec[i];
		m[vec[i]] = lim;
	}
	
//	for ( int i = 0 ; i < vec.size() ; ++ i ) {
//		cout << "vec[i]" << vec[i] << ", m[vec[i]] = " << m[vec[i]] << endl;
//	}
	
	// 查询 
	int res = 0;
	// 从后往前贴  如果要贴当前海报的区间[a,b)已经贴满了seg[k] = 1
	// 则说明这个海报会被覆盖掉
	// 若从前往后帖  则需贴完后遍历一遍 看有哪些可以露出 保存的状态不一样 
	for ( int i = N-1 ; i >= 0 ; -- i ) {
		int ll = m[st[i].l]-1;
		int rr = m[st[i].r];
		if ( update(ll, rr, 0, 0, lim) ) {
			res ++;
		}
	}
	
	printf( "%d\n", res );
	return 0;
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值