题意:
有一家酒店,又一排n个房间,一共m个询问,如果输入操作为1,接下来再输入一个a,表示来了一群a个人,希望能住在连续的a间房间,如果有连续n间房,输出最左边的连续的a间房间最左边的那个房间,如果没有输出0
如果输入操作为2,接下来会有两个数a,b表示有从a开始的连续b个人退房(无输出)
了解过线段树的一眼就能看出来这又是一个区间查询问题,可以用线段树辅助解决,但是与RMQ 的线段树不同,这个也相对较难一些。
本体的解题思路分一下步骤:
- 建树(不再详细介绍)
- 查询
- 更新
可分为以上四个步骤解题
先定义三个变量,(lsum)左连续表示从最左边开始的最长连续可用房间数,(rsum)右连续同上,(sum)连续表示该区间内最长的连续可用房间数
对于查询,我们可以这样操作,首先看一下整个大区间的最大连续长度是否大于所需要的,如果小于,说明肯定无法完成操作,直接返回0,如果可以,将区间从中间分开
- 左半边区间的sum够不够
- 左边的rsum和右半边的lsum相加够不够(意思就是两个区间的中间共同连续部分够不够用)
- 右半边的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的值的更新方式变成上述即可
为了减少复杂度,还应该采用懒标记,标记有三个内容
- 标记为0表示区间内无可用的房间
- 标记为1表示区间内房间全部可用
- 标记为-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;
}