You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

147 lines
6.3 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

#include <bits/stdc++.h>
using namespace std;
const int N = 200010;
// n个房间m个操作
int n, m;
// 线段树,最大空房数,左起最大空房数,右起最大空房数
struct Node {
int l, r; // 范围[l,r]
int mx; // 区间内最大连续空房数
int lx, rx; // 从左开始或从右开始的最大连续空房数
int tag; // tag:懒标记1 开房, :2 退房0: 默认值
} tr[N];
// 根据左右儿子的统计信息,更新父亲的统计信息,也就是更新父亲的统计属性
void pushup(int u) {
// 左后+右前,左半边最大长度,右半边最大长度,三者的最大值
// ① mx:区间内最长连续空房
tr[u].mx = max({tr[u << 1].rx + tr[u << 1 | 1].lx, tr[u << 1].mx, tr[u << 1 | 1].mx});
// ② tr[u].lx:父亲节点的最长左前缀,通过tr[u<<1],tr[u<<1|1]的信息来组装
int mid = (tr[u].l + tr[u].r) >> 1; // 这个mid一般都有用套路性的写上
// mid 是划给左儿子的,所以 mid - tr[u].l + 1 就是左儿子的控制范围,而tr[u].r - mid 就是右儿子的控制范围
// 结合实际场景利用左右儿子的信息更新父亲的lx,rx信息
// 如果左儿子整体区间都是空的,那么 父亲的左半边最大长度=左儿子区间长度+右儿子左半边最大长度
if (tr[u << 1].mx == mid - tr[u].l + 1)
tr[u].lx = tr[u << 1].mx + tr[u << 1 | 1].lx;
else
// 否则,左儿子的左半边最大长度,就是父亲的左半边最大长度
tr[u].lx = tr[u << 1].lx;
// ③ rx:最长右后缀
// 如果右儿子的整体区间都是空的,那么 父亲的右半边最长度=右儿子区间长度+左儿子右半边最大长度
if (tr[u << 1 | 1].mx == tr[u].r - mid)
tr[u].rx = tr[u << 1].rx + tr[u << 1 | 1].mx;
else // 否则,右儿子的右半边最大长度,就是父亲的右半边最大长度
tr[u].rx = tr[u << 1 | 1].rx;
}
// 构建线段树
void build(int u, int l, int r) {
tr[u].l = l, tr[u].r = r; // 标记范围
if (l == r) { // 叶子
// 初始时都是空房间,连续空房长度就是区间长度:1,
// 从本区间内左边向右数有1个空白房间
// 从右边向左数也有1个空白房间
tr[u].mx = tr[u].lx = tr[u].rx = 1;
return;
}
int mid = (l + r) >> 1;
build(u << 1, l, mid), build(u << 1 | 1, mid + 1, r);
pushup(u);
}
// 向下传递更新信息
void pushdown(int u) {
if (tr[u].tag) { // 如果存在懒标记
tr[u << 1].tag = tr[u << 1 | 1].tag = tr[u].tag; // 向左右儿子传递这个懒标记
// 根据实际的变化情况,修改左右儿子的统计信息
if (tr[u].tag == 1) { // 开房,则全部为1左、右儿子的三个统计信息都需要修改为0
tr[u << 1].mx = tr[u << 1].lx = tr[u << 1].rx = 0;
tr[u << 1 | 1].mx = tr[u << 1 | 1].lx = tr[u << 1 | 1].rx = 0;
} else { // 退房
int mid = (tr[u].l + tr[u].r) >> 1;
// 退房后整个区间全是0左半区间中 左最长=右最长=整个区间长=mid-l+1
// 退房后整个区间全是0右半区间中 左最长=右最长=整个区间长=r-mid
tr[u << 1].mx = tr[u << 1].lx = tr[u << 1].rx = mid - tr[u].l + 1;
tr[u << 1 | 1].mx = tr[u << 1 | 1].lx = tr[u << 1 | 1].rx = tr[u].r - mid;
};
// 将本节点的懒标记修改为0表示已经完成了懒标记的向下传递
tr[u].tag = 0;
}
}
void modify(int u, int l, int r, int v) {
if (tr[u].l >= l && tr[u].r <= r) { // 完全包含了当前区间[l,r],那么所有内部节点都需要进行修改
if (v == 1) // 1:开房
tr[u].mx = tr[u].lx = tr[u].rx = 0; // 统计信息全面修改为0因为全住上人了没有空的了
else // 2:退房
tr[u].mx = tr[u].lx = tr[u].rx = tr[u].r - tr[u].l + 1; // 退房嘛,整个区间都是空白的,左边最长=右边最长=整个区间最长
// 打上懒标记(有的懒标记是可以累加的,这里是不可以的)
tr[u].tag = v;
return;
}
// 下传懒标记
pushdown(u);
// 分裂
int mid = (tr[u].l + tr[u].r) >> 1;
if (l <= mid) modify(u << 1, l, r, v);
if (r > mid) modify(u << 1 | 1, l, r, v);
// 向上更新统计信息
pushup(u);
}
// 以u为根节点的树中控制范围是[l,r],找出连续空白房间个数>=x的区间,返回此区间的左边界
int query(int u, int x) {
// 分裂前先进行懒标记下传
pushdown(u);
int mid = (tr[u].l + tr[u].r) >> 1;
// 因为要获取最小的房间号,所以,必须是左,中,右这个顺序来进行检查,否则可能返回的不是最小房间号
if (tr[u << 1].mx >= x) return query(u << 1, x);
if (tr[u << 1].rx + tr[u << 1 | 1].lx >= x) return mid - tr[u << 1].rx + 1; // 直接找到开始点
if (tr[u << 1 | 1].mx >= x) return query(u << 1 | 1, x);
// 如果不存在x这么多个连续空白则返回0
return 0;
}
int main() {
// 文件输入输出
#ifndef ONLINE_JUDGE
freopen("P2894.in", "r", stdin);
#endif
// 加快读入
ios::sync_with_stdio(false), cin.tie(0);
cin >> n >> m;
// 构建线段树
build(1, 1, n);
while (m--) {
int op;
cin >> op;
if (op == 1) { // 查询连续x个空房 且 入住
int x;
cin >> x;
if (tr[1].mx >= x) { // 如果目前整体的最大值满足条件可以找出x个连续空房
int l = query(1, x);
printf("%d\n", l);
// 如果找到的合适的起点从l开始连续x个房间住上人
if (l) modify(1, l, l + x - 1, 1);
} else
puts("0"); // 找不到x个连续空房
} else { // 退房
int x, y;
cin >> x >> y;
// 2:退房
modify(1, x, x + y - 1, 2);
}
}
return 0;
}