HDU6756 Finding a MEX

前提是你 提交于 2020-08-16 00:23:59

原题链接

看到\(\operatorname{mex}\),一个显然的想法就是根号分治。我们设\(d_u\)为点\(u\)的度数,再设一个合适正整数\(L\),代表根号分治的临界值。将所有的点分为两个集合\(S_1,S_2\),使得\(\forall u\in S_1,d_u\le L;\forall u\in S_2,d_u>L\)。有一个显然的性质是\(|S_2|\le \big\lfloor \dfrac{2\times n}{L}\big\rfloor\)

先考虑查询。对于所有\(S_1\)中的点,因其邻居的个数最多是\(L\),所以珂以暴力枚举邻居算\(\operatorname{mex}\)。这个算法是珂以做到\(O(L)\)的(答案输出的\(\operatorname{mex}\)的最大值只能是\(n\),所以你珂以把所有大于\(n\)的数设置成\(n\))。对于所有\(S_2\)中的点,你就必须要对每个\(S_2\)中的点用一个奇技淫巧的数据结构来维护了。这个数据结构必须要支持全局查询\(\operatorname{mex}\)

先不谈这个数据结构要实现哪些东西,我们来看看修改。修改是单点修改。一个点的修改只会影响到它周边邻居的答案。设点\(u\)的邻居的集合为\(C_u\)。对于集合\(S_1\cap C_u\)中的所有点,珂以不用管它们,因为我们在询问时是暴力枚举计算的。所以我们只要考虑集合\(S_2\cap C_u\)中的所有点,这些点的个数最多也只有\(\big\lfloor \dfrac{2\times n}{L}\big\rfloor\)个。所以我们珂以暴力枚举这样的邻居,然后对于每一个邻居,用维护它的那个数据结构来修改。具体来说就是在这个数据结构中删除原有的数,再插入新添加的数。

所以这个数据结构要支持的就是:1. 插入; 2. 删除; 3. 查询全局\(\operatorname{mex}\)。那么问题来了,这个数据结构是啥呢?显然平衡树啊!每个节点维护两个值:\(val, cnt\)。这样询问时就直接二分,判断ls->size是否等于this->cnt即珂。

所以查询时的时间复杂度为\(O(L+\log n)\),修改的时间复杂度为\(O(\big\lfloor \dfrac{2\times n}{L}\big\rfloor \log n)\)。显然,由均值不等式,当\(L\)\(\sqrt {2\times n\log n}\)时,时间复杂度取到最优值为\(O(n\sqrt{n\log n})\)。(然后窝这个复杂度跑了\(655\)ms,勇获rnk\(2\),吊打了一堆\(O(n\sqrt{n\log n})\)\(O(n\sqrt{n})\)的,而且还没开Ofast)。

代码:

#include <bits/stdc++.h>
using namespace std;

constexpr int _I_Buffer_Size = 32 << 20;
static char _I_Buffer[_I_Buffer_Size];
char *_I_pos = _I_Buffer;
void read(int32_t *n) {
  while (*_I_pos < 48) _I_pos++;
  *n = *_I_pos++ - '0';
  while (*_I_pos > 47)
    *n = *n * 10 + (*_I_pos++ - '0');
}

constexpr int Maxn = 1e5 + 5, BL = 1024;

int n, m, q;
int a[Maxn];
vector<int> g[Maxn];
int deg[Maxn];
__attribute__((__always_inline__)) __inline
void add_edge(int eu, int ev) {
  g[eu].push_back(ev), deg[ev]++;
}
bool appear[Maxn];

namespace treap {
  constexpr int Maxs = ::Maxn * 127;
  static mt19937 rng(40113143); // 20051212 + 20060617 + 1314
  int ls[Maxs], rs[Maxs], fix[Maxs], size[Maxs], tot;
  int val[Maxs], cnt[Maxs];
  __attribute__((__always_inline__)) __inline
  void init_treap() {
    tot = 0;
    ls[0] = rs[0] = 0;
    size[0] = 0, fix[0] = 0;
    val[0] = cnt[0] = 0;
  }
  __attribute__((__always_inline__)) __inline
  int newnode(int v) {
    ++tot;
    ls[tot] = rs[tot] = 0;
    fix[tot] = rng();
    size[tot] = 1;
    val[tot] = v;
    cnt[tot] = 1;
    return tot;
  }
  __attribute__((__always_inline__)) __inline
  void pushup(int p) {
    size[p] = size[ls[p]] + size[rs[p]] + 1;
  }
  int join(int l, int r) {
    if (!l || !r) return l | r;
    if (fix[l] < fix[r]) {
      rs[l] = join(rs[l], r);
      pushup(l);
      return l;
    }
    else {
      ls[r] = join(l, ls[r]);
      pushup(r);
      return r;
    }
  }
  void split_s(int p, int s, int &l, int &r) {
    if (!p) l = r = 0;
    else {
      int lsize = size[ls[p]] + 1;
      if (lsize <= s) {
        l = p;
        split_s(rs[p], s - lsize, rs[l], r);
        pushup(l);
      }
      else {
        r = p;
        split_s(ls[p], s, l, ls[r]);
        pushup(r);
      }
    }
  }
  void split_v(int p, int v, int &l, int &r) {
    if (!p) l = r = 0;
    else {
      if (val[p] <= v) {
        l = p;
        split_v(rs[p], v, rs[l], r);
        pushup(l);
      }
      else {
        r = p;
        split_v(ls[p], v, l, ls[r]);
        pushup(r);
      }
    }
  }
  void insert(int &p, int x) {
    int A, B, C;
    split_v(p, x - 1, A, B);
    split_v(B, x, B, C);
    if (size[B] > 0) cnt[B]++;
    else B = newnode(x);
    p = join(join(A, B), C);
  }
  void erase(int &p, int x) {
    int A, B, C;
    split_v(p, x - 1, A, B);
    split_v(B, x, B, C);
    if (--cnt[B] == 0) B = 0;
    p = join(join(A, B), C);
  }
  int mex(int p) {
    int c = 0;
    while (p) {
      int lsize = size[ls[p]] + 1;
      if (lsize + c == val[p] + 1) {
        c += lsize;
        p = rs[p];
      }
      else {
        p = ls[p];
      }
    }
    return c;
  }
} // namespace treap
using namespace treap;
int root[Maxn];

void re_clear() {
  for (int i = 1; i <= n; ++i) {
    g[i].clear();
    deg[i] = 0;
  }
  memset(root, 0, (n + 1) << 2);
}

void Main() {

  read(&n), read(&m);
  for (int i = 1; i <= n; ++i) {
    read(a + i);
    if (a[i] > n) a[i] = n + 1;
  }
  for (int i = 1; i <= m; ++i) {
    int u, v;
    read(&u), read(&v);
    add_edge(u, v);
    add_edge(v, u);
  }

  static const auto cmp_node =
  [&](int x, int y)->bool {
    return deg[x] > deg[y];
  };
  for (int i = 1; i <= n; ++i)
    sort(g[i].begin(), g[i].end(), cmp_node);
  init_treap();
  for (int u = 1; u <= n; ++u)
    if (deg[u] > BL) {
      for (int &v: g[u]) treap::insert(root[u], a[v]);
    }

  read(&q);
  while (q--) {
    int op, u, x;
    read(&op), read(&u);
    if (op == 1) {
      read(&x);
      if (x > n) x = n + 1;
      for (int &v: g[u]) {
        if (deg[v] <= BL) break;
        treap::erase(root[v], a[u]);
        treap::insert(root[v], x);
      }
      a[u] = x;
    }
    else {
      if (deg[u] <= BL) {
        for (int &v: g[u])
          appear[a[v]] = true;
        for (int i = 0; ; ++i)
          if (!appear[i]) {
            printf("%d\n", i);
            break;
          }
        for (int &v: g[u])
          appear[a[v]] = false;
      }
      else {
        printf("%d\n", treap::mex(root[u]));
      }
    }
  }

  re_clear();

}

int main() {

  fread(_I_Buffer, 1, _I_Buffer_Size, stdin);

  int tests;
  read(&tests);
  while (tests--) Main();

  return 0;

}

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!