P3071 [USACO13JAN] Seating G 区间合并 区间修改
解题思路
这道题目需要实现一个座位管理系统,支持两种操作:
-
A p:安排p个人坐在最左边的连续p个空座位上,如果没有足够的连续空座位则计数失败
-
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倍原始数组大小的空间
关键点说明
-
线段树设计:维护了连续空座位的三种信息,可以高效处理区间查询和更新
-
懒标记:使用lazy标记来延迟更新,提高效率
-
查找算法:优先查找左子树,保证找到最左边的满足条件的连续空座位
-
更新操作:统一使用change函数,通过op参数区分空/占两种操作
这个实现能够高效处理题目给出的最大数据规模,保证在时间限制内完成所有操作。

浙公网安备 33010602011771号