poj3667 Hotel 线段树延迟更新 区间合并

Hotel
Time Limit: 3000MS Memory Limit: 65536K
Total Submissions: 15205 Accepted: 6583

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 r to 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


分析摘自: http://www.cnblogs.com/scau20110726/archive/2013/05/07/3065418.html

代码中的实现跟分析中所使用的变量名称不同


线段树 

题意:有一个线段,从1到n,下面m个操作,操作分两个类型,以1开头的是查询操作,以2开头的是更新操作

1 w  表示在总区间内查询一个长度为w的可用区间,并且要最靠左,能找到的话返回这个区间的左端点并占用了这个区间,找不到返回0 

好像n=10 , 1 3 查到的最左的长度为3的可用区间就是[1,3],返回1,并且该区间被占用了

2 a len , 表示从单位a开始,清除一段长度为len的区间(将其变为可用,不被占用),不需要输出

因此看sample的话就可以理解了

 

记录一下自己的感悟:

用线段树,首先要定义好线段树的节点信息,一般看到一个问题,很难很快能确定线段树要记录的信息
做线段树不能为了做题而做,首先线段树是一种辅助结构,它是为问题而生的,因而必须具体问题具体分析
回忆一下RMQ问题,其实解决RMQ有很多方法,根本不需要用到线段树,用线段树解决RMQ,其实是利用线段树的性质来辅助解决这个问题
回忆一下求矩形面积并或周长并的问题,一般使用的是扫描线法,其实扫描线法和线段树一点关系都没有,扫描线法应该归为计算几何的算法,
使用线段树只是为了辅助实现扫描线法

因而回到这题,要解,必须分析问题本质,才去思考怎么用线段树来辅助,另外为什么能用线段树辅助是可行的,这个问题似乎更有价值

1 查询操作,找一段长度为W的没被覆盖的最左的区间
2 更新操作,将某段连续的区域清空

更新操作相对容易解决,关键是怎么实现查询操作
既然是要找一段长度至少为W的区间,要做到这点,其实不难,我们可以在每个线段树的节点里增加一个域tlen,表示该区间可用的区间的最大长度,
至于这个tlen区间的具体位置在哪里不知道,只是知道该区间内存在这么一段可用的区间,并且注意,这个tlen表示的是最大长度,该节点可能有多段可用的区间,但是最长的长度是tlen
记录了这个信息,至少能解决一个问题,就是能不能找到一个合适的区间。如果查询的区间长度W > 总区间的tlen,那么查询一定是失败的(总区间中可以的最大区间都不能满足那就肯定失败)
但这远远不够,其一查询是要返回区间的具体位置的,这里无法返回位置,另外是要查询最左区间,最左的且满足>=W的区间可能不是这个tlen区间

那么我们进一步思考这个问题
首先我们先增加两个域,llen,rlen
llen表示一个区间从最左端开始可用的且连续的最大长度
例如区间[1,5],覆盖情况为[0,0,0,1,1],llen = 3,从最左端有3格可以利用
区间[1,5],覆盖情况为[1,0,0,0,0],llen = 0,因为从最左端开始找不到1格可用的区间
rlen表示一个区间从最右端开始可用的且连续的最大长度
例如区间[1,5],覆盖情况为[1,0,1,0,0],rlen = 2,从最右端有2格可以利用
区间[1,5],覆盖情况为[0,0,0,0,1],rlen = 0,因为从最右端开始找不到1格可用的区间
对于一个区间,我们知道它左半区间的tlen,和右半区间的tlen,如果左半区间的tlen >= W ,那么我们一定能在左边找到(满足最左),所以可以深入到左半区间去确定该区间的具体位置
如果左端的不满足,那么我们要先考虑横跨两边的区间(因为要满足最左),因而记录的llen,rlen可以派上用场,一段横跨的区间,
那么是 左边区间rrlen + 右边区间llen ,如果满足的话,就是该区间了,它的位置也是可以确定的
如果横跨的区间不满足,那么就在右半区间找,如果右半区间的tlen >= W , 那么可以在右半区间找到,所以深入到右半区间去确定它的具体位置,否则的话,整个查询就失败了

可见查询是建立在tlen,llen,rlen这个信息之上的,而每次查询后其实伴随着修改,而且还有专门的修改操作,这些修改操作都会改变tlen,llen,rlen的值,所以在更新的时候是时刻维护这些信息

关于这3个信息的维护

当前区间的tlen = max{ 左半区间tlen , 右半区间tlen , 左半区间rlen+右半区间llen} (这个不难理解吧,取左右较大的那个,或者横跨中间的那个)

如果左半区间全部可以用: 当前区间llen = 左半区间llen(tlen) + 右半区间llen 
左半区间部分能用: 当前区间llen = 左半区间llen

如果右半区间全部能用: 当前区间rlen = 右半区间rlen(tlen) + 左半区间rlen
右半区间部分能用: 当前区间rlen = 右半区间rlen

这样就全部维护好了



#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#define lson l, m, rt << 1
#define rson m + 1, r, rt << 1 | 1

using namespace std;

const int maxn = 55555;
int	msum[maxn << 2], rsum[maxn << 2], lsum[maxn << 2];
int col[maxn << 2];

void PushDown(int rt, int m) {
	//col[rt] != -1说明这个地方打过延迟标记,且还没有向下更新
	//需要向下更新才能对子树中的节点进行操作
	if (col[rt] != -1) {
		col[rt << 1] = col[rt << 1 | 1] = col[rt];
		//如果col[rt]为0的话,则说明房间空出来了,将左右子区间更新为原来长度
		//如果col[rt]为1的话,则说明房间占用,左右子区间可用房间数置为0
		msum[rt << 1] = rsum[rt << 1] = lsum[rt << 1] = col[rt] ? 0 : (m - (m >> 1));
		msum[rt << 1 | 1] = rsum[rt << 1 | 1] = lsum[rt << 1 | 1] = col[rt] ? 0 : (m >> 1);
		//说明这个地方的延迟标记已经向下更新过了,打上-1
		col[rt] = -1;
	}
}

void PushUp(int rt, int m) {
	lsum[rt] = lsum[rt << 1];
	rsum[rt] = rsum[rt << 1 | 1];
	//若左区间最大左连续长度为左区间总长度长度
	//则加上右区间最大左连续长度
	//即比如: 0000 0011  -->   4 + 2 = 6
	if (lsum[rt] == (m - (m >> 1))) {
		lsum[rt] += lsum[rt << 1 | 1];
	}
	//同上考虑
	if (rsum[rt] == (m >> 1)) {
		rsum[rt] += rsum[rt << 1];
	}
	//当前区间的最大连续可用长度为跨左右区间的连续长度,左区间最大连续长度和右区间最大连续长度三者最大值
	msum[rt] = max(rsum[rt << 1] + lsum[rt << 1 | 1], max(msum[rt << 1], msum[rt << 1 | 1]));
}

void build(int l, int r, int rt) {
	msum[rt] = lsum[rt] = rsum[rt] = r - l + 1;
	col[rt] = -1; //标记-1表示没有打上延迟标记
	if (l == r) return;
	int m = (l + r) >> 1;
	build(lson);
	build(rson);
}

int query(int w, int l, int r, int rt) {
	if (l == r) return l;
	//先更新子区间信息
	PushDown(rt, r - l + 1);
	int m = (l + r) >> 1;
	//左区间优先考虑,若左区间可以容纳,则查询左区间
	if (msum[rt << 1] >= w) {
		return query(w, lson);
	} //否则若跨左右区间的连续长度可以容纳,则直接计算返回起始位置
	else if (rsum[rt << 1] + lsum[rt << 1 | 1] >= w) {
		return m - rsum[rt << 1] + 1;
	} //否则若右区间可以容纳,则查询右区间
	else if (msum[rt << 1 | 1] >= w) {
		return query(w, rson);
	}
}

void update(int L, int R, int c, int l, int r, int rt) {
	if (L <= l && r <= R) {
		//若c为0,则表示退房操作,将可用长度恢复
		//若c为1,则表示占用,将可用长度置为0
		msum[rt] = lsum[rt] = rsum[rt] = c ? 0 : r - l + 1;
		col[rt] = c; //打上延迟标记
		return;
	}
	//先更新子区间信息
	PushDown(rt, r - l + 1);
	int m = (l + r) >> 1;
	if (L <= m)
		update(L, R, c, lson);
	if (R > m)
		update(L, R, c, rson);
	//根据子区间信息更新父区间信息
	PushUp(rt, r - l + 1);
}

int main()
{
	int N, M, op, a, b;
	scanf("%d%d", &N, &M);
	build(1, N, 1);
	while (M--)	{
		scanf("%d%d", &op, &a);
		if (op == 1) {
			//如果总区间上的最长连续区间都没有a大的话
			//直接打印0
			if (msum[1] < a) {
				puts("0");
				continue;
			}
			else {
				int ans = query(a, 1, N, 1);
				printf("%d\n", ans);
				//将新住入的房间的col标记为1表示占用
				update(ans, ans + a - 1, 1, 1, N, 1);
			}
		}
		else {
			scanf("%d", &b);
			//将退的房间的col标记为0表示空房间
			update(a, a + b - 1, 0, 1, N, 1);
		}
	}
	return 0;
}


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值