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.

163 lines
7.3 KiB

2 years ago
##[$P2894$ [$USACO08FEB$]$Hotel$ $G$](https://www.luogu.com.cn/problem/P2894)
### 一、题目描述
参考样例,第一行输入$nm$ $n$代表有$n$个房间,编号为$1-\sim n$,开始都为 **空房**$m$表示以下有$m$行操作,以下每行先输入一个数 $op$ ,表示一种操作:
若$op=1$,表示查询房间$query$,再输入一个数$x$,表示在$1 \sim n$ 房间中找到 **长度为$x$的连续空房**,输出连续$x$个房间中 **左端的房间号**,尽量让这个房间号最小:
* 若找不到长度为$x$的连续空房,输出$0$
* **若找得到,在这$x$个空房间中住上人**
若$op=2$,表示退房,再输入两个数 $xy$ 代表 房间号 $x \sim x+y-1$ 退房,即让房间为空。
### 二、解题思路
![](https://dsideal.obs.cn-north-1.myhuaweicloud.com/HuangHai/BlogImages/{year}/{month}/{md5}.{extName}/202308230852428.png)
### 三、实现代码
```cpp {.line-numbers}
#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;
}
```