Wonderful Impostors

注意:全文左闭右开。

Hint 0: CF 的数据结构题难度自动 -500。


Hint 1: 若干个需求可能被同时满足的充分必要条件是什么?对于单次询问,如何暴力回答?

将所有没有内鬼的区间并起来,记作 ,那么 中的任意一个人都是船员。倘若存在一个有内鬼的区间是 的子集,便出现了冲突。

我们可以开一颗值域为 的线段树,初始为 ,将所有没有内鬼的区间在线段树上执行区间 操作。然后对于所有有内鬼的区间挨个检查,若这段区间的最小值不是 ,则说明这段区间被完全包含掉了。


Hint 2:在线处理询问好像很难做。有什么离线处理的方法吗?

每个区间的回答都是 YES 或 NO,并且一段区间 的子区间的回答一定不会劣于 。由此我们联想到 [NOI2024] 集合 中的双指针离线。具体来说,固定左边界(记为 ),一直向右移动右边界直到出现冲突(记此时的右边界为 ),此时对于左边界为 的所有询问,其右边界小于 的都合法,大于等于 的都不合法。


Hint 3:当新增一条区间内有内鬼的条件,该如何检查加入该条件后是否会产生冲突?请注意你的实现应该易于从中删除一组询问。

跟暴力做法类似,开一颗值域为 的线段树,初始为 。每加入一个区间内没有内鬼的条件,就在线段树上对应范围执行区间 操作。想要检查添加区间存在内鬼的条件后会不会出现冲突,只需要判断对应区间的最小值是不是 就好了。删除也很简单,执行区间 操作即可。


Hint 4:当新增一条区间内没有内鬼的条件,该如何检查加入该条件后是否会产生冲突?

最为复杂的情况如图。黑色表示不存在内鬼的约束,红色表示存在内鬼的约束。

若干个区间并起来的结果为若干个互不相交的大区间。添加一个新的区间只会对其中一小部分有影响,其他的不变。有影响的大区间范围可以通过在上面所说的线段树上二分取得。具体来讲,通过线段树二分找到第一个为 的数。

找到一个大区间 后,还需要判断这个大区间有没有完全包含一个存在内鬼的区间。这相当于要处理一个二维偏序问题:是否存在二元组 满足 ?再开一颗单点修改、区间 min 线段树,将第一关键字 作为下标,线段树上存放所有第一关键字为 的二元组中最小的 。在新增 / 删除区间有内鬼的约束时,同步维护一个数组套 multiset,下标为 的 multiset 存放所有第一关键字为 的二元组所对应的 ,在线段树上同步取其最小值。查询时,查询 的区间最小值是否小于 即可。

// https://github.com/Mr-Python-in-China/mp-oi-library/blob/main/library/
#include <mrpython/typical_segment_tree.hpp>
#include <mrpython/lazy_segment_tree.hpp>
#include <iostream>
#include <set>
using namespace std;
istream& fin = cin;
ostream& fout = cout;
using ui = unsigned int;
using uli = unsigned long long int;
using li = long long int;
struct Question {
  bool c;
  size_t l, r;
};
struct AddMinNode {
  using T = ui;
  using Lazy = ui;
  static T merge_data(T l, T r) { return min(l, r); }
  static Lazy merge_lazy(T l, T r) { return l + r; }
  static T operate(T x, Lazy y, size_t) { return x + y; }
};
int main(void) {
  ios::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr);
  size_t T;
  fin >> T;
  while (T--) {
    size_t n, m;
    fin >> n >> m;
    vector<Question> a(m);
    for (auto& i : a) {
      char c;
      fin >> c;
      i.c = c == '1';
      fin >> i.l >> i.r;
      --i.l;
    }
    size_t q;
    fin >> q;
    vector<pair<size_t, size_t>> ql(q);
    for (auto& [l, r] : ql) fin >> l >> r, --l;
    mrpython::lazy_segment_tree_from_node<AddMinNode>::type rgn(n, 0, 0);
    mrpython::typical_segment_tree_min<size_t> rgr(n, n + 1);
    vector<multiset<size_t>> rgv(n, {n + 1});

    vector<size_t> ans(m);
    for (size_t dl = 0, dr = 0; dl < m; ++dl) {
      auto check = [&](Question const& t) {
        if (t.c)
          return !rgn.getd(t.l, t.r);
        else {
          size_t rl = rgn.find_last_left(t.l - 1, [](ui x) { return x == 0; }) +
                      1,
                 rr = rgn.find_first_right(t.r, [](ui x) { return x == 0; });
          return rgr.get(rl, rr) > rr;
        }
      };
      while (dr < m && check(a[dr])) {
        if (a[dr].c) {
          rgv[a[dr].l].emplace(a[dr].r);
          rgr.set(a[dr].l, mrpython::const_function(*rgv[a[dr].l].begin()));
        } else
          rgn.set(a[dr].l, a[dr].r, 1);
        ++dr;
      }
      ans[dl] = dr;
      if (a[dl].c) {
        rgv[a[dl].l].erase(rgv[a[dl].l].find(a[dl].r));
        rgr.set(a[dl].l, mrpython::const_function(*rgv[a[dl].l].begin()));
      } else
        rgn.set(a[dl].l, a[dl].r, -1);
    }

    for (auto [l, r] : ql) fout << (r <= ans[l] ? "YES\n" : "NO\n");
  }
  return 0;
}
posted @ 2025-04-26 00:42  MrPython  阅读(9)  评论(0)    收藏  举报  来源