P2894 [USACO08FEB] Hotel G 线段树 区间修改、合并

解题思路

这道题目要求实现一个酒店房间管理系统,支持两种操作:

  1. 入住操作:查找并分配最左边的连续x个空房间

  2. 退房操作:释放指定区间的房间

这是一个典型的区间维护问题,需要使用线段树来高效处理动态区间查询和更新。

方法选择

代码使用了线段树来维护以下信息:

  • maxlen:区间内最长连续空房间数

  • ls:区间左端点开始的连续空房间数

  • rs:区间右端点结束的连续空房间数

  • lazy:懒标记,用于延迟更新(1-入住,2-退房)

代码详细注释

#include<bits/stdc++.h>
#define lc rt << 1      // 左子节点索引
#define rc rt << 1 | 1  // 右子节点索引
#define lson lc,l,mid   // 左子树参数:左子节点,区间[l,mid]
#define rson rc,mid + 1,r // 右子树参数:右子节点,区间[mid+1,r]
#define ll long long 
using namespace std;

const int N = 5e5 + 10, inf = 0x3f3f3f3f;

// 线段树节点结构体
struct node{
    int maxlen;  // 区间内最长连续空房间数
    int ls;      // 从左端点开始的连续空房间数
    int rs;      // 从右端点结束的连续空房间数
    int lazy;    // 懒标记:0-无操作,1-入住标记,2-退房标记
};

node t[N << 2];  // 线段树数组
int n, m;        // n-房间总数,m-操作次数

// 更新父节点信息
void pushup(int rt, int l, int r) {
    int mid = (l + r) >> 1;
    
    // 更新左连续空房间数
    t[rt].ls = t[lc].ls;
    if(t[lc].ls == mid - l + 1) {  // 如果左子树全部为空
        t[rt].ls += t[rc].ls;      // 可以延伸到右子树的左连续
    }
    
    // 更新右连续空房间数
    t[rt].rs = t[rc].rs;
    if(t[rc].rs == r - mid) {      // 如果右子树全部为空
        t[rt].rs += t[lc].rs;      // 可以延伸到左子树的右连续
    }
    
    // 更新区间最长连续空房间
    t[rt].maxlen = max(t[lc].maxlen, t[rc].maxlen);  // 左右子树的最大值
    t[rt].maxlen = max(t[rt].maxlen, t[lc].rs + t[rc].ls);  // 跨越左右子树的连续空房间
}

// 下放懒标记操作
void down(int rt, int l, int r, int op) {
    if(op == 1) {  // 入住操作,标记为已占用
        t[rt] = {0, 0, 0, 1};  // 全部置为占用状态
    }
    if(op == 2) {  // 退房操作,标记为空房
        int len = r - l + 1;
        t[rt] = {len, len, len, 2};  // 全部置为空状态
    }
}

// 下推懒标记到子节点
void pushdown(int rt, int l, int r) {
    if(t[rt].lazy == 0) return;  // 无懒标记则返回
    
    int mid = (l + r) >> 1;
    down(lson, t[rt].lazy);  // 下放到左子树
    down(rson, t[rt].lazy);  // 下放到右子树
    t[rt].lazy = 0;          // 清空当前节点的懒标记
}

// 构建线段树
void build(int rt, int l, int r) {
    if(l == r) {  // 叶子节点
        int len = r - l + 1;
        t[rt] = {len, len, len, 0};  // 初始全部为空房
        return;
    }
    int mid = (l + r) >> 1;
    build(lson);  // 构建左子树
    build(rson);  // 构建右子树
    pushup(rt, l, r);  // 更新父节点信息
}

// 区间更新操作
void change(int rt, int l, int r, int x, int y, int op) {
    if(r < x || y < l) return;  // 完全不在区间内
    
    if(x <= l && r <= y) {  // 完全包含在区间内
        if(op == 1) {  // 入住操作
            t[rt] = {0, 0, 0, 1};
        }
        if(op == 2) {  // 退房操作
            int len = r - l + 1;
            t[rt] = {len, len, len, 2};
        }
        return;
    }
    
    pushdown(rt, l, r);  // 下放懒标记
    int mid = (l + r) >> 1;
    change(lson, x, y, op);  // 更新左子树
    change(rson, x, y, op);  // 更新右子树
    pushup(rt, l, r);        // 更新父节点信息
}

// 查找最左边能容纳x个连续空房间的位置
int find(int rt, int l, int r, int x) {
    if(t[rt].maxlen < x) return -1;  // 整个区间都无法容纳
    
    pushdown(rt, l, r);  // 下放懒标记
    
    int mid = (l + r) >> 1;
    
    // 优先查找左子树
    if(t[lc].maxlen >= x) {
        return find(lson, x);
    }
    
    // 检查跨越左右子树的连续空房间
    if(t[lc].rs + t[rc].ls >= x) {
        return mid - t[lc].rs + 1;  // 计算起始位置
    }
    
    // 最后查找右子树
    return find(rson, x);
}

int main() {
    cin >> n >> m;
    build(1, 1, n);  // 初始化线段树
    
    while(m--) {
        int op, x, y;
        scanf("%d", &op);
        
        if(op == 1) {  // 入住操作
            scanf("%d", &x);
            int pos = find(1, 1, n, x);  // 查找位置
            if(pos == -1) {
                printf("0\n");  // 查找失败
            } else {
                change(1, 1, n, pos, pos + x - 1, 1);  // 标记为已占用
                printf("%d\n", pos);  // 输出起始房间号
            }
        }
        
        if(op == 2) {  // 退房操作
            scanf("%d%d", &x, &y);
            change(1, 1, n, x, x + y - 1, 2);  // 标记为空房
        }
    }
    return 0;
}

 

posted @ 2025-05-21 12:29  CRt0729  阅读(9)  评论(0)    收藏  举报