【poj3667】Hotel

Time Limit: 3000MS   Memory Limit: 65536K
Total Submissions: 16299   Accepted: 7077

Description

The cows are journeying north to Thunder Bay in Canada to gain cultural enrichment and enjoy a vacation on the sunny shores of Lake Superior. Bessie, ever the competent travel agent, has named the Bullmoose Hotel on famed Cumberland Street as their vacation residence. This immense hotel has N (1 ≤ N ≤ 50,000) rooms all located on the same side of an extremely long hallway (all the better to see the lake, of course).

The cows and other visitors arrive in groups of size Di (1 ≤ Di ≤ N) and approach the front desk to check in. Each group i requests a set of Di contiguous rooms from Canmuu, the moose staffing the counter. He assigns them some set of consecutive room numbers r..r+Di-1 if they are available or, if no contiguous set of rooms is available, politely suggests alternate lodging. Canmuu always chooses the value of rto be the smallest possible.

Visitors also depart the hotel from groups of contiguous rooms. Checkout i has the parameters Xi and Di which specify the vacating of rooms Xi ..Xi +Di-1 (1 ≤ Xi ≤ N-Di+1). Some (or all) of those rooms might be empty before the checkout.

Your job is to assist Canmuu by processing M (1 ≤ M < 50,000) checkin/checkout requests. The hotel is initially unoccupied.

Input

* Line 1: Two space-separated integers: N and M
* Lines 2..M+1: Line i+1 contains request expressed as one of two possible formats: (a) Two space separated integers representing a check-in request: 1 and D(b) Three space-separated integers representing a check-out: 2, Xi, and Di

Output

* Lines 1.....: For each check-in request, output a single line with a single integer r, the first room in the contiguous sequence of rooms to be occupied. If the request cannot be satisfied, output 0.

Sample Input

10 6
1 3
1 3
1 3
1 3
2 5 5
1 6

Sample Output

1
4
7
0
5

Source

【题解】

这题要用线段树来做。具体一点。

设节点rt所代表的区间为begin,end;

设llianxusum[rt]表示从begin开始往后数的最大连续空房间数目(遇到有人住的就停止);

比如这段区间为0 0 0 1 (1表示有人住)

则llianxusum[rt] = 3;

而如果是1 0 0 0则llianxusum[rt] = 0;

设rlianxusum[rt]表示从end开始往前数的最大连续空房间数目(遇到有人住的就停止);

同理0 1 0 0

rlianxusum[rt] = 2;

0 1 1 0

rlianxusum[rt] = 1;

然后设totallianxusum[rt]表示在[begin,end]这段区间内的最大连续空房间数目(不一定从最左端begin开始);

之所以这样设在于。我们所要选择的一系列的空房间只有3种可能。

第一种:这一段空房间全部在rt<<1节点所代表的区间内

第二种:这一段空房间全部在rt<<1|1节点所代表的区间内。

第三种:这一段空房间一部分在rt<<1节点所代表的区间,另外一部分在rt<<1|1节点所代表的区间内。

更具体一点我们逐一解决问题。

首先我们解决询问的时候;

如果totallianxusum[rt<<1](也即左儿子)>=房间需求量,则往左递归。

然后不能单纯就看右儿子了。还有横跨两个儿子的情况。

即如果rlianxusum[rt<<1]+llianxusum[rt<<1|1]>=房间需求量,则可以直接输出答案了。

即(l+r)/2 - rlianxusum[rt<<1]+1;(可以笔算一下。)

然后如果这个不满足,那就肯定是在右儿子了。(一开始sum[1]>=需求量的判断已经否定了答案不存在的情况);

然后就是往上更新的时候的一些具体操作以及对懒惰标记的处理问题了。

看注释吧。

【代码】

#include <cstdio>

int n, m;
int totallianxusum[200001], llianxusum[200001], rlianxusum[200001],lazy_tag[200001];

void build(int l, int r, int rt)
{
	totallianxusum[rt] = llianxusum[rt] = rlianxusum[rt] = r - l + 1; //一开始房间都是空的。所以那些什么标志都是区间的长度。
	lazy_tag[rt] = -1;//一开始没有懒惰标记。
	if (l == r) //如果上下界是一样的,则退出。
		return;
	int mid = (l + r)>>1;//取中点
	build(l, mid, rt << 1);//往左递归建树
	build(mid + 1, r, rt << 1 | 1);//往右递归建树。
}

void push_down(int rt, int len) //查看rt这个节点是否有懒惰标记,如果有的话就往下更新儿子节点。
{
	if (lazy_tag[rt] != -1)
	{
		lazy_tag[rt << 1] = lazy_tag[rt];//把儿子节点的懒惰标记赋值为父亲节点的懒惰标记。
		lazy_tag[rt << 1 | 1] = lazy_tag[rt];
		if (lazy_tag[rt] == 1)//如果这个懒惰标记是要把这些房间都占据。
		{
			totallianxusum[rt << 1] = llianxusum[rt << 1] = rlianxusum[rt << 1] = 0;//这个区间内所有的房间都被占据,剩余房间为0
			totallianxusum[rt << 1 | 1] = llianxusum[rt << 1 | 1] = rlianxusum[rt << 1 | 1] = 0;//这是右儿子的
		}
		else//如果这个懒惰标记是要把这些房间都搬空。
		{
			totallianxusum[rt << 1] = llianxusum[rt << 1] = rlianxusum[rt << 1] = (len-(len>>1));//把这些房间都搬空。然后空房子的数目就是区间长度。
			totallianxusum[rt << 1 | 1] = llianxusum[rt << 1 | 1] = rlianxusum[rt << 1 | 1] = len>>1;//右儿子同理。
		}
		lazy_tag[rt] = -1;//然后把这个懒惰标记给去掉。
	}
}

int query(int w, int l, int r, int rt)//询问是否存在连续w个房间,当前节点为rt,下界为l,上界为r.
{
	if (l == r) //如果到了区间只剩一个数字的情况。那w肯定是等于1的。则直接返回这个数字就可以了。
	{
		return l;
	}
	push_down(rt,r-l+1);//处理一下懒惰标记,往下更新儿子节点。
	int m = (l + r) >> 1;//取这个区间的中点。
	if (totallianxusum[rt << 1] >= w)//如果左区间内的空房子数目大于等于所需要的量。则往左区间找房子。
		return query(w, l, m, rt << 1);
	else
		if (rlianxusum[rt << 1] + llianxusum[rt << 1 | 1] >= w)//如果横跨两个区间的连续房子数目够处理w个需求,则直接返回第一个的坐标
			return m - rlianxusum[rt << 1] + 1;//这个表达式可以用笔算验证一下。
		else
			return query(w, m + 1, r, rt << 1 | 1);//因为一开始有判断,所以只要是进了这个void,肯定是有解的,则可以直接不用判断进行右儿子递归。
}

int max(int a, int b) //返回a和b中的较大值。
{
	return a > b ? a : b;
}

void push_up(int rt, int len) //往上更新父亲节点。
{
	llianxusum[rt] = llianxusum[rt << 1];//父亲节点一开始从最右左开始连续空房子数目就等于左儿子的相应值。
	if (llianxusum[rt] == len - (len >> 1))//然后如果整个左儿子的区间都是空的,那么就可以加上右区间的从最左开始的连续空房子数目作为
		//整个区间的从最左开始的最大连续空房子数目。
		llianxusum[rt] += llianxusum[rt << 1 | 1];
	rlianxusum[rt] = rlianxusum[rt << 1 | 1];//父亲节点一开始从右开始连续的空房子数目就等于右儿子的相应值。
	if (rlianxusum[rt] == len >> 1)//如果整个右区间都是空的。则可以再累加上左区间的最右那一部分连续的空房子数目。
		rlianxusum[rt] += rlianxusum[rt << 1];
	totallianxusum[rt] = max(totallianxusum[rt << 1], totallianxusum[rt << 1|1]);//然后整个区间不要求从最左或最右开始的连续空房子数目
	//就等于左区间的和右区间中的相应值的较大值。
	totallianxusum[rt] = max(totallianxusum[rt], rlianxusum[rt << 1] + llianxusum[rt << 1 | 1]);
	//或者是左区间的最右边那串连续的空房子加上右区间最左边的那串连续的空房子数目的和。
	//取较大值即可。
}

void updata(int l, int r, int num,int begin, int end, int rt) //所要修改的房子区间为l,r,
{//当前节点rt所代表的区间为begin,end;要将房子修改为num;
	if (l <= begin && end <= r)//如果当前节点的区间被所要求的区间覆盖
	{
		int what = 0;//如果是要占据它们,空房子数目就变为0
		if (num == 0)//如果是要讲其搬空
			what = end-begin+1;//则空房子数目为区间的长度。
		totallianxusum[rt] = llianxusum[rt] = rlianxusum[rt] = what;//因为这个操作是对整个区间的,则整个区间的相关值都会变成一样的。
		lazy_tag[rt] = num;//标一个懒惰标记。
		return;
	}
	push_down(rt, end - begin + 1);//看看rt这个节点有没有懒惰标记,有的话就往下更新儿子节点。
	int m = (begin + end) >> 1;//获取这个节点的区间的中点
	if (l <= m)
		updata(l, r, num, begin, m, rt << 1);//如果这个节点所代表的区间和所求的区间有相交的地方。则不断逼近那个交集。
	if (m < r)
		updata(l, r, num, m + 1, end, rt << 1 | 1);//右边的交集同理
	push_up(rt,end-begin+1);//然后因为可能更新了儿子节点。所以要更新相应的父亲节点。
}

int main()
{
	//freopen("F:\rush.txt", "r", stdin);
	//freopen("F:\rush_out.txt", "w", stdout);
	scanf("%d%d", &n, &m);
	build(1, n, 1); //一开始建树。
	for (int i = 1; i <= m; i++)
	{
		int op, a, b;
		scanf("%d", &op);//读取操作
		if (op == 1)//如果是住入操作
		{
			scanf("%d", &a);
			if (a > totallianxusum[1])//如果整个区间都没有符合要求的连续空房间则输出无解
			{
				printf("0
");
			}
			else//否则找出这个区间的开始位置在哪里。
			{
				int p = query(a, 1, n, 1);
				printf("%d
",p);//输出这个开始位置
				updata(p, p + a - 1, 1,1, n, 1);//然后把开始位置,连同自身的a个位置都赋值为1,表示占据。
			}
		}
		else
		{
			scanf("%d%d", &a, &b);//如果是退房
			updata(a, a + b - 1, 0, 1, n, 1);//就把相应区间的值改为0,表示退房。
		}
	}
	return 0;
}



原文地址:https://www.cnblogs.com/AWCXV/p/7632273.html