P2894 [USACO08FEB]Hotel G

P2894 [USACO08FEB]Hotel G

一、题目描述

参考样例,第一行输入nmn代表有n个房间,编号为1n,开始都为 空房m表示以下有m行操作,以下每行先输入一个数 op ,表示一种操作:

op=1,表示查询房间query,再输入一个数x,表示在1n 房间中找到 长度为x的连续空房,输出连续x个房间中 左端的房间号,尽量让这个房间号最小:

  • 若找不到长度为x的连续空房,输出0
  • 若找得到,在这x个空房间中住上人

op=2,表示退房,再输入两个数 xy 代表 房间号 xx+y1 退房,即让房间为空。

二、解题思路

三、实现代码

#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;
}
posted @   糖豆爸爸  阅读(20)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!
历史上的今天:
2018-01-02 完美解决doc、docx格式word转换为Html
Live2D
点击右上角即可分享
微信分享提示