POJ 3667 Hotel(线段树)

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

    题目要求的是查询一段区间,更新一段区间,这是线段树最擅长的事了!

    该题有两个主要操作,订房和退房,其实可以把订房分解为两个操作,查询(query)和更新(update),而退房就是更新(update)。

    本题最难的地方在于节点状态的设计,它需要有三个额外的域:lval(记录该节点从左边开始的连续未订房间的数目), rval(记录该节点从右边开始的连续未订房间的数目), sval(记录该节点最大连续未订房间的数目),有了这三个域,每次更新子节点的状态后,返回父节点时,需要根据子节点的状态来同步更新父节点的状态。在更新一段区间的时候,不必每次都更新到最下层,可以使用lazy思想,这里的lazy思想也不需要做标记,可以根据当前节点sval的值来判断,如果当前节点的sval的值为0,那么它的子节点的sval一定为0, 如果当前节点的sval为它的区间长度,那么它的子节点的sval也一定为区间长度,详见代码。

#include <cstdio>

#define MAX 50005
#define max(a, b) (a > b ? a : b)

struct Node
{
	int left, right;
	int lval;//记录该节点从左边开始的连续未订房间的数目
	int rval;//记录该节点从右边开始的连续未订房间的数目
	int sval;//记录该节点最大连续未订房间的数目
}tr[4*MAX];

//建树
void build(int left, int right, int root)
{
	tr[root].left = left, tr[root].right = right;
	tr[root].lval = tr[root].rval = tr[root].sval = right - left + 1;
	if(left < right)
	{
		int mid = (left+right) >> 1;
		build(left, mid, root<<1);
		build(mid+1, right, (root<<1) | 1);
	}
}

//查询
int query(int a, int root)
{
	if(tr[root].sval < a)	return 0;
	if(tr[root].lval >= a)	return tr[root].left;
	if(tr[root<<1].sval >= a)
		return query(a, root<<1);
	else if(tr[root<<1].rval + tr[(root<<1) | 1].lval >= a)
		return tr[root<<1].right - tr[root<<1].rval + 1;
	else
		return query(a, (root<<1) | 1);
}

//获得节点root的区间长度
int getLength(int root)
{
	return tr[root].right - tr[root].left + 1;
}

//根据val的值重置节点
void resetNode(int val, int root)
{
	tr[root].lval = tr[root].rval = tr[root].sval = getLength(root)*val;
}

//更新节点状态,更新时采用lazy思想,所以不用每次都更新到最下层
void update(int left, int right, int val, int root)
{
	if(tr[root].left == left && tr[root].right == right)
	{
		resetNode(val, root);
		return;
	}

	//lazy思想
	if(tr[root].sval == 0)
	{
		resetNode(0, root<<1);
		resetNode(0, (root<<1) | 1);
	}
	if(tr[root].sval == getLength(root))
	{
		resetNode(1, root<<1);
		resetNode(1, (root<<1) | 1);
	}

	int mid = (tr[root].left + tr[root].right) >> 1;
	if(right <= mid)
		update(left, right, val, root<<1);
	else if(left > mid)
		update(left, right, val, (root<<1) | 1);
	else
	{
		update(left, mid, val, root<<1);
		update(mid+1, right, val, (root<<1) | 1);
	}

	//区间合并
	tr[root].lval = tr[root<<1].lval;
	if(tr[root<<1].lval == getLength(root<<1))
		tr[root].lval += tr[(root<<1) | 1].lval;
	tr[root].rval = tr[(root<<1) | 1].rval;
	if(tr[(root<<1) | 1].rval == getLength((root<<1) | 1))
		tr[root].rval += tr[root<<1].rval;
	tr[root].sval = max(max(tr[root<<1].sval, tr[(root<<1) | 1].sval), tr[root<<1].rval + tr[(root<<1) | 1].lval);
}

int main()
{
	int N, M, c, a, b, p;
	scanf("%d %d", &N, &M);
	build(1, N, 1);
	for(int i=0; i<M; i++)
	{
		scanf("%d", &c);
		if(c == 1)
		{
			scanf("%d", &a);
			//在这里把订房操作分为两步
			//首先查询是否有符合要求的区间,然后更新
			p = query(a, 1);
			if(p)
				update(p, p+a-1, 0, 1);
			printf("%d\n", p);
		}
		else
		{
			scanf("%d %d", &a, &b);
			update(a, a+b-1, 1, 1);
		}
	}
	return 0;
}


 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值