P2894 [USACO08FEB]Hotel G
[]
一、题目描述
参考样例,第一行输入 ,代表有个房间,编号为,开始都为 空房,表示以下有行操作,以下每行先输入一个数 ,表示一种操作:
若,表示查询房间,再输入一个数,表示在 房间中找到 长度为的连续空房,输出连续个房间中 左端的房间号,尽量让这个房间号最小:
- 若找不到长度为的连续空房,输出
- 若找得到,在这个空房间中住上人
若,表示退房,再输入两个数 代表 房间号 退房,即让房间为空。
二、解题思路
三、实现代码
#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;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!
2018-01-02 完美解决doc、docx格式word转换为Html