题解:洛谷 AT_abc451_c Understory
【题目来源】
洛谷:AT_abc451_c [ABC451C] Understory - 洛谷
【题目描述】
高桥正在管理他花园里的树的数量。最开始,花园里没有任何树。
给出 \(Q\) 个操作,每个操作有以下两种类型之一。在处理完每个操作后,立即输出当前花园中树的数量。
1 h:在花园中种下一棵高度为 \(h\) 的新树。2 h:将花园中所有高度不超过 \(h\) 的树全部移除。
【输入】
输入通过标准输入给出,格式如下:
\(Q\)
\(\text{query}_1\)
\(\text{query}_2\)
⋮
\(\text{query}_Q\)
第 \(i\) 个操作 \(\text{query}_i\) 为以下两种之一:
\(1\) \(h\)
\(2\) \(h\)
【输出】
输出 \(Q\) 行。
第 \(i\) 行输出在处理完第 \(i\) 个操作后,当前花园中树的数量。
【输入样例】
5
1 5
1 7
1 8
2 7
1 3
【输出样例】
1
2
3
1
2
【核心思想】
-
问题分析:维护一个动态树高集合,支持两种操作:插入高度 \(h\) 的树,删除所有高度 \(\leq h\) 的树。每次操作后输出当前树木数量。这是一个动态集合维护 + 区间删除问题,核心在于高效支持"删除所有 \(\leq h\) 的元素"这一操作。
-
算法选择:
- 多重集合(Multiset)+ 区间删除:利用
multiset的有序性和erase(begin, it)的区间删除功能,在 \(O(\log n + k)\) 时间内删除所有高度 \(\leq h\) 的树(\(k\) 为删除数量)
- 多重集合(Multiset)+ 区间删除:利用
-
关键步骤:
- 读入数据:\(Q\),以及 \(Q\) 个查询
- 操作处理:
1 h:trees.insert(h),插入高度 \(h\)2 h:it = trees.upper_bound(h),找到第一个高度 \(> h\) 的位置;trees.erase(trees.begin(), it),删除 \([\text{begin}, it)\) 内所有元素(即高度 \(\leq h\) 的所有树)
- 输出:每次操作后
trees.size()
-
时间/空间复杂度:
- 时间复杂度:\(O(Q \log N)\),每次插入 \(O(\log N)\),每次删除 \(O(\log N + k)\)(\(k\) 为删除数量,均摊分析下总删除次数不超过总插入次数)
- 空间复杂度:\(O(N)\),存储当前所有树的高度
-
多重集合与区间删除的核心思想:
- 有序性利用:
multiset内部按高度有序排列,upper_bound(h)返回第一个 \(> h\) 的迭代器,其左侧所有元素即为高度 \(\leq h\) 的树 - 区间删除的高效性:
erase(begin, it)一次性删除区间 \([\text{begin}, it)\) 内的所有元素,时间复杂度与删除元素数量线性相关,避免了逐个删除的 \(O(k \log n)\) 开销 - 动态计数:
size()在 \(O(1)\) 时间内返回当前集合大小,满足每次操作后立即输出的要求 - 多重集合的必要性:允许相同高度的树存在(不同树高度可以相同),
multiset支持重复元素,而set不支持 - 适用于动态有序集合维护、区间批量删除类问题
- 有序性利用:
【算法标签】
普及- #集合
【代码详解】
#include <bits/stdc++.h>
using namespace std;
int q; // 查询次数
multiset<int> trees; // 多重集合存储树的高度
int main()
{
cin >> q; // 输入查询次数
while (q--) // 处理每个查询
{
int op, h;
cin >> op >> h; // 输入操作类型和高度
if (op == 1) // 操作1:种树
{
trees.insert(h); // 插入一棵高度为h的树
}
else // 操作2:砍树
{
auto it = trees.upper_bound(h); // 找到第一个高度>h的树
trees.erase(trees.begin(), it); // 删除所有高度≤h的树
}
cout << trees.size() << endl; // 输出剩余树的数量
}
return 0;
}
【运行结果】
5
1 5
1
1 7
2
1 8
3
2 7
1
1 3
2
浙公网安备 33010602011771号