Tunnel Warfare - HDU 1540
Link
题目分析
本题是一个线段树单点修改和区间查询的问题。
在题目中,每个点都是两两相连,整个区间内的点都是相互连通的。
根据题意,可以得到题目的操作:
- 如果点\(x\)能被访问,那就删除点\(x\),即解除它与它相邻两点的联通关系
- 询问点\(x\)与多少个点相连
- 如果点\(x\)被删除,那就恢复\(x\),使其与相邻的两点相连
我们可以将整个区间看作整个联通块:
- 如果点\(x\)被删除,则产生了两个新联通块
- 查询\(x\)与多少个点相连,等价查询\(x\)所在点联通块有多少个点
- 恢复同理
我个人认为,本题的难点在于求\(x\)所在的联通块有多少个点。我们可以用一个set
来存储被删除的点的下标,比如:对于序列\([1,\dots ,n]\),删除点\(3\)和\(10\),那么序列就变成了\([1,2, 3, \dots , 10, 11,\dots, n]\),当我们要查询与\(5\)相连的点的数量,只需要获取线段树中区间\([3,5]\)中点的数量。
注意
本题细节比较多,需要注意以下几点
-
在
set
中查询被\(x\)的左右区间时:- 寻找右区间时,
set
中第一个大于\(x\)的下标就是查询的右区间;如果不存在,那么查询的右区间则为\(n\) - 寻找左区间时,先找第一个大于等于\(x\)的下标:
- 如果找到的下标等于\(x\),说明\(x\)本身是被删除的点,返回0
- 上述情况不满足时,如果找到的下标是
set
中最小的下标,那么查询的左边界是\(1\);如果不是,左区间为\(j-1\)
- 寻找右区间时,
-
注意回复操作次数超过之前删除的操作数
-
set
本身会除去重复元素,由于存在一个点被多次删除的情况,所以删除set
中的某个下标前,要判断是否存在
Code
#include <iostream>
#include <algorithm>
#include <set>
using namespace std;
constexpr int N = 50050;
struct Node {
int l, r;
int sum;
} tr[N * 4];
int n, m, stk[N], top, x;
set<int> se;
inline void push_up(int u) {
tr[u].sum = tr[u << 1].sum + tr[u << 1 | 1].sum;
}
void build(int u, int l, int r) {
if (l == r) tr[u] = {l, r, 1};
else {
tr[u] = {l, r};
int mid = l + r >> 1;
build(u << 1, l, mid), build(u << 1 | 1, mid + 1, r);
push_up(u);
}
}
void modify(int u, int x, int c) {
if (tr[u].l == x && tr[u].r == x) tr[u].sum = c;
else {
int mid = tr[u].l + tr[u].r >> 1;
if (x <= mid) modify(u << 1, x, c);
if (x > mid) modify(u << 1 | 1, x, c);
push_up(u);
}
}
int query(int u, int l, int r) {
if (tr[u].l >= l && tr[u].r <= r) return tr[u].sum;
else {
int mid = tr[u].l + tr[u].r >> 1, cnt = 0;
if (l <= mid) cnt = query(u << 1, l, r);
if (r > mid) cnt += query(u << 1 | 1, l, r);
return cnt;
}
}
int main() {
char op[2];
while (~scanf("%d%d", &n, &m)) {
se.clear();
top = 0;
build(1, 1, n);
while (m --) {
scanf("%s", op);
if (*op == 'R') {
if (top) {
int t = stk[top --];
modify(1, t, 1);
// 判断要删除的元素是否存在
auto it = se.lower_bound(t);
if (it != se.end() && *it == t) se.erase(it);
}
} else {
scanf("%d", &x);
if (*op == 'D') {
// 删除操作,同时记录每次的操作
modify(1, x, 0);
stk[++top] = x;
se.insert(x);
}
else if (*op == 'Q') {
int l, r;
// 二分查找左右边界
auto i = se.lower_bound(x), j = se.upper_bound(x);
if (i != se.end() && *i == x) { puts("0"); continue; }
else {
if (i == se.begin()) l = 1;
else l = *(--i);
}
if (j == se.end()) r = n;
else r = *j;
printf("%d\n", query(1, l, r));
}
}
}
}
return 0;
}