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;
}
posted @ 2025-03-28 20:09  XYukari  阅读(19)  评论(0)    收藏  举报