线段树区间合并(poj 3667 hotel)

题目链接

 题意:

有一家酒店,又一排n个房间,一共m个询问,如果输入操作为1,接下来再输入一个a,表示来了一群a个人,希望能住在连续的a间房间,如果有连续n间房,输出最左边的连续的a间房间最左边的那个房间,如果没有输出0

如果输入操作为2,接下来会有两个数a,b表示有从a开始的连续b个人退房(无输出)

了解过线段树的一眼就能看出来这又是一个区间查询问题,可以用线段树辅助解决,但是与RMQ 的线段树不同,这个也相对较难一些。

本体的解题思路分一下步骤:

  1. 建树(不再详细介绍)
  2. 查询
  3. 更新 

可分为以上四个步骤解题

先定义三个变量,(lsum)左连续表示从最左边开始的最长连续可用房间数,(rsum)右连续同上,(sum)连续表示该区间内最长的连续可用房间数

对于查询,我们可以这样操作,首先看一下整个大区间的最大连续长度是否大于所需要的,如果小于,说明肯定无法完成操作,直接返回0,如果可以,将区间从中间分开

  1. 左半边区间的sum够不够
  2. 左边的rsum和右半边的lsum相加够不够(意思就是两个区间的中间共同连续部分够不够用)
  3. 右半边的sum够不够

ps:这样判断的顺序不能变,因为题目要求的是要输出  最左边  的可用区间

如果符合第一个条件就递归到左区间内进行查询(因为可能左连续不一定是最大,例如(0,1,1,1,0),最大连续为3,左连续为0)

如果符合第三个同上

如果符合第二个条件,就可以直接输出最左边的下标(因为除了这一部分,其余的都已经包含在左右两边了)

至于lsum和rsum的值,应该是左子树的lsum的值和右子树的rsum的值(ps:特判一下,如果lsum等于rsum分别等于区间的总长度的话,还应该对应的加上右子树的lsum和左子树的rsum)

而sum的值则是左子树的sum和右子树的sum以及中间共同连续部分(左子树rsum+右子树lsum)取最大值

更新区间值的时候和RMQ的线段树类似,只是在递归回来的时候,把lsum,rsum,sum的值的更新方式变成上述即可

为了减少复杂度,还应该采用懒标记,标记有三个内容

  1. 标记为0表示区间内无可用的房间
  2. 标记为1表示区间内房间全部可用
  3. 标记为-1表示无需操作 

建树的时候,将所有的标记全部设为-1,在进行查询操作时,一旦找到了最左边的连续可用区间,将从该点开始连续一定长度的区间变为不可用,及标记为0,在更新时,将给定的从某一点a开始的长度为b的一段区间变为可用的,及标记设为1

在更新和查询的过程中,看一下当前的标记是不是-1,如果不是,说明需要进行标记下传操作,并在下传后将该位置的标记重新置为-1

代码如下

#include<iostream>
#include<cmath>
using namespace std;
#define endl "\n"
struct p {
	int l, r, lsum, rsum, sum, mark;
	int getlen() {
		return r - l + 1;
	}
	void getsum() {
		lsum = rsum = sum = (mark != 0 ? getlen() : 0);
	}
}c[200200];
void build(int l, int r, int k) {//建树 
	c[k].l = l;
	c[k].r = r;
	c[k].mark = -1;
	c[k].getsum();

	if (l == r)return;
	int mid = (l + r) / 2;
	build(l, mid, k * 2);
	build(mid + 1, r, k * 2 + 1);
}
int query(int w, int k) {//查询 
	if (c[k].l == c[k].r&&w == 1)return c[k].l;
	if (c[k].mark != -1) {//标记下传
		c[k * 2].mark = c[k * 2 + 1].mark = c[k].mark;
		c[k * 2].getsum();
		c[k * 2 + 1].getsum();
		c[k].mark = -1;
	}
	if (c[k].sum >= w) {
		if (c[k * 2].sum >= w)return query(w, k * 2);
		if (c[k * 2].rsum + c[k * 2 + 1].lsum >= w)return c[k * 2].r - c[k * 2].rsum + 1;
		if (c[k * 2 + 1].sum >= w)return query(w, k * 2 + 1);
	}
	else return 0;
}
void update(int l, int r, int mk, int k) {//更新
	if (l <= c[k].l&&c[k].r <= r) {
		c[k].mark = mk;
		c[k].getsum();
		return;
	}
	if (l > c[k].r || r < c[k].l)return;
	if (c[k].mark != -1) {//标记下传
		c[k * 2].mark = c[k * 2 + 1].mark = c[k].mark;
		c[k * 2].getsum();
		c[k * 2 + 1].getsum();
		c[k].mark = -1;
	}
	int mid = (c[k].l + c[k].r) / 2;
	if (r <= mid)update(l, r, mk, k * 2);
	else if (mid < l)update(l, r, mk, k * 2 + 1);
	else {
		update(l, r, mk, k * 2);
		update(l, r, mk, k * 2 + 1);
	}
	c[k].rsum = c[k * 2 + 1].rsum;
	c[k].lsum = c[k * 2].lsum;
	if (c[k * 2].lsum == c[k * 2].getlen())c[k].lsum += c[k * 2 + 1].lsum;
	if (c[k * 2 + 1].rsum == c[k * 2 + 1].getlen())c[k].rsum += c[k * 2].rsum;
	c[k].sum = max(c[k * 2 + 1].sum, max(c[k * 2].sum, c[k * 2].rsum + c[k * 2 + 1].lsum));
}
int n, m;
int op, a, b;
int main()
{
#ifdef endl
	cin.tie(0);
	cout.tie(0);
	ios::sync_with_stdio(0);
#endif
	cin >> n >> m;
	build(1, n, 1);
	while (m--) {
		cin >> op;
		if (op == 1) {
			cin >> a;
			int res = query(a, 1);
			cout << res << endl;
			if (res) {
				update(res, res + a - 1, 0, 1);
			}
		}
		else if (op == 2) {
			cin >> a >> b;
			update(a, a + b - 1, 1, 1);
		}
	}
	return 0;
}

 

posted @ 2019-07-26 14:25  correct  阅读(104)  评论(0)    收藏  举报