P3071 [USACO13JAN] Seating G 区间合并 区间修改

解题思路

这道题目需要实现一个座位管理系统,支持两种操作:

  1. A p:安排p个人坐在最左边的连续p个空座位上,如果没有足够的连续空座位则计数失败

  2. L a b:释放[a,b]区间的座位

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

方法选择

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

  • maxlen:区间内最长连续空座位

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

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

  • sum:区间内空座位总数(虽然实际未使用)

  • lazy:懒标记,用于延迟更新

代码详细注释

#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{
    ll maxlen;  // 区间内最长连续空座位数
    ll ls;      // 从左端点开始的最长连续空座位数
    ll rs;      // 从右端点结束的最长连续空座位数
    ll sum;     // 区间内空座位总数(实际未使用)
    ll 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);  // 跨越左右子树的连续空座位
    
    t[rt].sum = t[lc].sum + t[rc].sum;  // 更新空座位总数(实际未使用)
}

// 下放懒标记到子节点
void down(int rt, int l, int r, int lazy) {
    if(lazy == 1) {  // 标记为空座位
        int len = r - l + 1;
        t[rt] = {len, len, len, len, 1};  // 全部置为空
    }
    if(lazy == 2) {  // 标记为已占用
        t[rt] = {0, 0, 0, 0, 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) {
    t[rt].lazy = 0;
    if(l == r) {  // 叶子节点
        int len = r - l + 1;
        t[rt] = {len, 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) {  // 标记为空座位
            int len = r - l + 1;
            t[rt] = {len, len, len, len, 1};
        }
        if(op == 2) {  // 标记为已占用
            t[rt] = {0, 0, 0, 0, 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);  // 初始化线段树
    
    int ans = 0;  // 记录失败次数
    
    while(m--) {
        char op[2];
        int x, y;
        scanf("%s%d", op, &x);
        
        if(op[0] == 'A') {  // 安排座位操作
            int p = find(1, 1, n, x);  // 查找位置
            if(p == -1) {
                ans++;  // 查找失败
            } else {
                change(1, 1, n, p, p + x - 1, 2);  // 标记为已占用
            }
        }
        
        if(op[0] == 'L') {  // 释放座位操作
            scanf("%d", &y);
            change(1, 1, n, x, y, 1);  // 标记为空座位
        }
    }
    
    cout << ans;  // 输出失败次数
    return 0;
}

复杂度分析

  • 时间复杂度

    • 构建线段树:O(n)

    • 每次查询/更新:O(logn)

    • 总复杂度:O(n + mlogn)

  • 空间复杂度:O(n),线段树需要4倍原始数组大小的空间

关键点说明

  1. 线段树设计:维护了连续空座位的三种信息,可以高效处理区间查询和更新

  2. 懒标记:使用lazy标记来延迟更新,提高效率

  3. 查找算法:优先查找左子树,保证找到最左边的满足条件的连续空座位

  4. 更新操作:统一使用change函数,通过op参数区分空/占两种操作

这个实现能够高效处理题目给出的最大数据规模,保证在时间限制内完成所有操作。

posted @ 2025-05-21 11:43  CRt0729  阅读(11)  评论(0)    收藏  举报