P2161 [SHOI2009] 会场预约
你需要维护一个在数轴上的线段的集合 \(S\),支持两种操作:
A l r表示将 \(S\) 中所有与线段 \([l,r]\) 相交的线段删去,并将 \([l,r]\) 加入 \(S\) 中。B查询 \(S\) 中的元素数量。
对于 A 操作,每次还需输出删掉的元素个数。
这道题可以用 std::set + 重载运算符的技巧过掉,具体的说,我们将线段写成结构体,并重载小于号:
struct Seg {
int l, r;
bool operator<(const Seg& o) const {
return r < o.l;
}
};
考虑当我们插入一条线段 \(seg\) 时,set 会怎样工作。左端点在 \(seg\) 的右端点右边的线段,会排在 \(seg\) 的右边;右端点在 \(seg\) 左端点左边的线段,会排在 \(seg\) 的左边。它们与 \(seg\) 不相交。
而当我们查找 \(seg\),“最小的”线段是 set 中第一个和 \(seg\) 相交的线段(在这个规则下,相交就类似相等)。所以对于 A 操作,我们从这个线段出发,一个一个删掉和 \(seg\) 相交的线段即可。因为最多有 \(n\) 次操作,删除也是 \(O(n\log n)\) 的。
这个 trick 相当于说,重载小于号不一定要做一个偏序关系,可以跳出固定思维,让它表示一些特殊的关系,方便解决实际问题。其实如果用 multiset,可以一下子 erase 掉所有的相交的线段,更加方便。
参考:https://www.luogu.com.cn/article/sprx0wtn
下面是 AC 代码:
#include <bits/stdc++.h>
using namespace std;
struct Seg {
int l, r;
bool operator<(const Seg& o) const {
return r < o.l;
}
};
int main() {
cin.tie(0)->sync_with_stdio(0);
cout.tie(0);
int n;
cin >> n;
set<Seg> s;
for (int i = 0; i < n; i++) {
string op;
cin >> op;
if (op == "A") {
int l, r, cnt = 0;
cin >> l >> r;
Seg seg(l, r);
auto it = s.find(seg);
while (it != s.end()) {
s.erase(it);
it = s.find(seg);
cnt++;
}
cout << cnt << '\n';
s.insert(seg);
} else {
cout << s.size() << '\n';
}
}
return 0;
}

浙公网安备 33010602011771号