代码里画图注释了一些重点操作
typedef pair<int, int> PII;
const int INF = 2e9;
#define x first
#define y second
class RangeModule {
public:
set<PII> S;
RangeModule() {
S.insert({-INF, -INF});
S.insert({INF, INF});
}
void addRange(int left, int right) {
// |-----------------| (left, right)
// |------| (S里一个左端点与left重合的区间, 右端点如果小于right的话,
// lower_bound就认为它是小于(left, right)的, 就不会返回了.
// 为了应对这种情形, 把要查找的右区间设为-INF)
auto i = S.lower_bound({left, -INF});
i--;
// |-------| (i)
// |---------------| (left, right)
if (i->y < left) i++; // i与当前区间无交集, 看i的下一个
// 加1后的i也可能没有交集
// |-------| (上次的i) |--------| (加1后的i)
// |---------------| (left, right)
if (i->x > right) { // 说明当前区间与所有区间都无交集
S.insert({left, right});
} else {
// 此时 i 一定与当前区间有交集了, 顺序向后扫描, 找最后一个与当前区间有交集的区间j
// |---------| (i) |-----| |--------------| (j)
// |-----------------------------| (left, right)
auto j = i;
while (j->x <= right) j++;
j--;
// 最后要插入的合并后的大区间
PII t({min(i->x, left), max(j->y, right)});
// 注意不能在这里就插入区间t, 因为后面要删除i到j这个区间, 这里插入t的话
// i和j的迭代器有可能失效
// 顺序删掉 [i,j]之间的所有区间, 包括i和j
while (i != j) {
auto k = i;
k++;
S.erase(i);
i = k;
}
S.erase(j);
S.insert(t);
}
}
bool queryRange(int left, int right) {
// 要找能完全覆盖当前区间的区间, 所以要找左端点小于等于left的区间,
// upper_bound返回严格大于left的, 减 1 后就是小于等于的. 为了和插入时类似的情形, 如果有个区间如下:
// |------------------|
// |----------| (left, right)
// 这个区间的左端点和left重合, 右端点大于right, 那么会被upper_bound返回, 这是如果 i-- 的话就不对了
// 为了代码统一处理, 这里把要找的区间右端点设为正无穷, 这样和left左端点重合的所有区间都不会大于它, 所以
// 返回的 i 一定是左区间 *大于* left的, 将他减1后就会找到<=left的区间了.
auto i = S.upper_bound({left, INF});
i--;
return i->y >= right;
}
// 返回a区间里减去b区间剩下的所有区间
vector<PII> get(PII a, PII b) {
vector<PII> res;
if (a.x < b.x) {
if (a.y > b.y) {
// |--------------------------------| (a)
// |--------------| (b)
res.push_back({a.x, b.x});
res.push_back({b.y, a.y});
} else {
// |---------------| (a)
// |-------------------| (b)
res.push_back({a.x, b.x});
}
} else {
if (a.y > b.y)
// |-------------------| (a)
// |--------------| (b)
res.push_back({b.y, a.y});
}
return res;
}
void removeRange(int left, int right) {
// 操作和 insertRange 类似
auto i = S.lower_bound({left, -INF});
i--;
if (i->y < left) i++;
if (i->x > right) { // 说明当前区间与已有区间无交集
;//S.insert({left, right}); // 没有交集就什么也不用干了
} else {
// 删除完成后, 最后要把i的左半部分和j的右半部分插回集合里
// |--| (i的左半边) |---------| (j的右半边)
// |---------| (i) |-----| |--------------| (j)
// |-----------------------------| (left, right)
// 如果在端点处重合, 本不该算有交集的, 不过会在后面get函数里再加回来, 所以和插入操作一样处理也可以
// |-------------| (i) |------| |---------------| (j)
// |---------------------|
auto j = i;
while (j->x <= right) j++;
j--;
// update: i和j重合时, 如果i完全包含(left,right), 则删除后会留下左右两半部分
// |----------------------------------------| (i)
// |----------------------| (left, right)
// 所以get返回的应该是一个vector, 而不是单个区间
auto a = get(*i, {left, right});
auto b = get(*j, {left, right});
while (i != j) {
auto k = i;
k++;
S.erase(i);
i = k;
}
S.erase(j);
// 把前面搜到的 a 和 b 集合里的所有区间都加回来
// i和j重合时, 重复插入也没关系, 因为set会去重
for (auto x : a) S.insert(x);
for (auto x : b) S.insert(x);
}
}
};