传送门
题目大意就是说有n个村庄以及m个操作,这m个操作分别是:
- D x 毁坏一个编号为x的村庄
- R 重建最后一个毁坏的村庄
- Q x 询问编号为x的村庄直接或间接可以连接到的编号总数
虽说这道题是线段树区间合并的模板题,但使用平衡树(本人使用的是STL库中强大的集合容器set代替手写平衡树)亦为此题的一个经典做法。
我们分析一下,使用一个序列(用set维护)和一个栈维护被毁坏的村庄(使用栈是为了支持R操作),接下来解释如何用以上数据结构去支持三个操作
- D操作
有了对上述数据结构的清楚认识,这个问题十分容易解决,直接将x插入到序列及栈中去即可。 - R操作
类似的,当栈是非空是可执行R操作,具体就是在序列中删除栈顶的元素,再从栈中弹出该元素即可。 - Q操作
支持Q操作需要对平衡树(set就是一个被封装的可以直接使用的平衡树,其内部实现采用红黑树)的基本操作有所了解,首先在序列中查找该节点,这个可以用lower_bound实现,lower_bound的功能是在指定范围内找到第一个权值不小于待查询权值的结点。如果lower_bound找到的目标就是x,那么说明x已经被毁坏(因为set中存储的结点都是被毁坏的村庄编号),输出0,否则,在序列中找到它的前驱和后继,这两个值相减再减一就是我们需要的答案。其实上面的lower_bound操作已经帮我们完成了查找后继这一操作,但STL中并没有查找前驱的函数,那怎么办呢?其实也不难,STL的lower_bound函数返回的是一个迭代器,这个迭代器指向的就是后继,既然x不在set中,那么迭代器向前一位不就是x的前驱了嘛(实现set的红黑树是二叉平衡树,满足二叉搜索树的性质,在set中迭代器向前或向后移动一位相当于在一个有序的序列中指针向前或向后移动一位)!这对于没有学过平衡树的同学刚开始理解起来可能有点困难,仔细想想这个事还是很显然的。
最后为了简化操作,set中需要预先存贮0和n+1,但这两个值不用放进栈中,因为它们只是被我用来简化Q操作而增设的虚拟村庄,本身就不存在被毁坏这个概念。
真心觉得这道题一定要用STL编一遍,虽然速度有点慢(STL本身的瓶颈),但写了之后对set也会有更多的了解,同时对于使用迭代器也会有更深的体会。
Code C o d e
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<stack>
#include<set>
std::stack< int > Stack;
std::set<int> Set;
std::set<int>::iterator it; //迭代器
int main() {
int n, m;
while (~scanf("%d%d", &n, &m)) {
Set.clear(); Set.insert(0); Set.insert(n + 1); //set初始化
while (!Stack.empty()) Stack.pop(); //stack初始化
for(int i = 1; i <= m; i++) {
char c;
std::cin >> c;
if (c == 'D') {
int x;
scanf("%d", &x);
Set.insert(x);
Stack.push(x);
}
if(c == 'Q') {
int x;
scanf("%d", &x);
it = Set.lower_bound(x); //在set中找到第一个大于等于x的值
if (*it == x) { //如果x在序列中
puts("0");
continue;
}
printf("%d\n", *it - *(--it) - 1); //后继减前驱减一
}
if (c == 'R') {
if (Stack.empty()) continue; //保证栈非空
it = Set.find(Stack.top()); //查找
Stack.pop(); //stack弹出
Set.erase(it); //在set中删除
}
}
}
return 0;
}