题解 QOJ8557【Goldberg Machine】

题目描述

Z 市有 \(n\) 个路口,\(n-1\) 条道路连接这些路口。第 \(i\) 个路口有 \(c_i\) 条道路以其为端点,按逆时针方向分别直接到达路口 \(a_{i,0},a_{i,1},...a_{i,c_i-1}\),每个路口可以通过道路直接或间接到达所有路口。每个路口有个可以转的路标,初始指向去往 \(a_{i,t_i}\) 的道路。

Alice 计划从路口 \(1\) 出发,走过若干条道路。由于她没有 Z 市的地图,她会使用右手定则行动。假设她位于路口 \(x\),她会先让这里的路标转至其逆时针方向的下一条道路,即 \(t'_x=(t_x+1)\bmod c_x\),然后沿着转向前路标指向的道路前进,到达 \(a_{x,t_x}\)。当她再次回到 \(x\) 时,就会去往 \(a_{x,t'_x}\),并令 \(t''_x=(t'_x+1)\bmod c_x\)。巧合的是,她的朋友 Bob 有地图,如果你能回答如下问题,他就会把地图交给 Alice。

\(q\) 次操作,每次操作形如:

1 x y,表示把 \(t_x\) 设为 \(y\)

2 k,假设 Alice 从 \(1\) 出发,求她连续走过 \(k\) 条道路后到达的点。操作 \(2\) 互相独立,询问后会将所有路标复原。

\(1\le n,q\le 10^5,1\le c_i,a_{i,j}\le n,0<t_i<c_i,1\le x\le n,0<y<c_x,1\le k\le 10^{18}\)

特殊性质 A:\(c_1=1,c_i\le 2\)

solution

将每个点的父亲边旋转到边集的末尾,并定义指针指向边集的第一条边的点为正规点,反之为凌乱点。任何点被访问并返祖之后都会变为正规点。

对于一条链的情况,每个点要么是正规点,要么是凌乱点。正规点访问到会继续向下递归,凌乱点访问到会马上返祖,同时自己变成正规点。每次从 \(1\) 出发的时候,会经过一个前缀的正规点,然后触碰到凌乱点之后会立即返祖,一路返回直到根节点。因此我们可以维护那些点是正规点,每次询问二分是第几次从 \(1\) 走下去,可以通过一些数据结构维护,计算这时的步数。

扩展到一般树的情况,不妨定义一次旅程从指针指向第一条边的 \(1\) 出发,回到指针指向第一条边的 \(1\) 的过程。一次旅程会遍历一次正规点,同时会遍历到一些凌乱点,凌乱点自身又要往下递归,使其子树内某些点正规化。不妨更加精确的刻画该过程,我们可以维护每个点在第几次旅程中正规化,具体地对每个点,它在正规化的时候会访问指针后到父亲的那部分儿子,指针前的儿子需要下一次旅程才会访问到。所以对每个点维护时间戳,每个点对它指针前到父亲的子树做子树加,然后就可以尝试二分。

但是这个行走方式迫使我们需要维护欧拉环游序,欧拉环游序部分也是尝试维护环游序中的每个位置被正规化的时间戳,也是一些子树加(也就是区间加)。然后询问的时候二分第几次旅程,可以发现第 \(i\) 次旅程就是会访问时间戳 \(\leq i\) 的环游序位置,因此计算前 \(i\) 次旅程的总步数可以转化为计算 \(\sum_{j}(i-\min(t_j, i))\) 其中 \(t_j\) 是时间戳。知晓第几次旅程后就可以直接暴力从时间戳 \(\leq i\) 的点上选出第 \(k\) 个。

现在需要做的操作是:

  • \(O(q)\) 次区间 \(\pm1\)
  • \(O(q\log n)\) 次全局查询 \(\sum_{i}(k-\min(t_i, k))\) 转化为 \(\sum_{i}\min(t_i, k)\)
  • \(O(q)\) 次全局查询 \(\leq k\) 的数的第 \(idx\) 个。

分块,每一块内维护原数组和排序后的数组。第一个操作分为整块和散块,前者打标记,后者暴力重构。第二个操作转化为 \(\leq k\) 的数有多少个以及它们的和,在排序后的数组上二分,同时维护一个前缀和直接查询。第三种操作转化为查询 \(\leq k\) 的数有多少个,跳过若干整块后直接暴力。为了不让复杂度多太多 \(\log\),观察到可以对每一块维护在块内二分某个数会到达的位置(注意到值域是 \(O(n)\)),每个单点 \(\pm1\) 都只会对它产生 \(O(1)\) 的影响。第二个操作的前缀和部分在每次作为散块执行第一个操作时重构。于是令块长为 \(B\) 可以做到 \(O(n/B+B)-O(n/B)-O(n/B+B)\) 的复杂度,平衡得到 \(O(\sqrt {n\log n})\)

code

#include <bits/stdc++.h>
using namespace std;
#ifdef LOCAL
#define debug(...) fprintf(stderr, ##__VA_ARGS__)
#else
#define endl "\n"
#define debug(...) void(0)
#endif
using LL = long long;
namespace ds_bf {
  int c[200010];
  void add(int l, int r, int k) {
    if (l > r) return ;
    for (int i = l; i <= r; i++) c[i] += k;
  }
  LL sum(int n, int k) {
    LL res = 0;
    for (int i = 1; i <= n; i++) res += min(k, c[i]);
    return res;
  }
  int select(int n, LL k, int idx) {
    assert(idx > 0);
    for (int i = 1; i <= n; i++) if (c[i] <= k && --idx == 0) return i;
    return -1;
  }
};
namespace ds {
  constexpr int B = 1400, N = 1e5 + 10;
  struct block {
    int elem[B], tag, ptr[N], val[B];
    LL psm[B];
    block() {
      memset(elem, 0, sizeof elem);
      memset(val, 0, sizeof val);
      memset(psm, 0, sizeof psm);
      tag = 0;
      fill(ptr + 1, ptr + N, B);
    }
    void add_value(int v) { ++val[--ptr[v + 1]]; }
    void del_value(int v) { --val[ptr[v]++]; }
    void buildsum() {
      psm[0] = val[0];
      for (int i = 1; i < B; i++) psm[i] = psm[i - 1] + val[i];
    }
    void _add(int l, int r) {
      for (int i = l; i <= r; i++) add_value(elem[i]++);
      buildsum();
    }
    void _del(int l, int r) {
      for (int i = l; i <= r; i++) del_value(elem[i]--);
      buildsum();
    }
    void add(int l, int r, int k) { return k == 1 ? _add(l, r) : _del(l, r); }
    LL calc(int k) { // sum_x min(x, k) [WA]
      int pos = k >= tag ? ptr[k - tag] : 0;
      return (pos ? psm[pos - 1] + 1ll * pos * tag: 0) + 1ll * (B - pos) * k;
    }
    int count(int k) { // sum_x [x <= k]
      return k + 1 >= tag ? ptr[k - tag + 1] : 0;
    }
    int select(int k, int idx) { // idx-th x, x <= k
      for (int i = 0; i < B; i++) if (elem[i] <= k - tag && --idx == 0) return i;
      throw logic_error("ds::block::select: wrong argument");
    }
  } blk[N * 2 / B + 10];
  void add(int l, int r, int k) {
    debug("add(%d, %d, %d)\n", l, r, k);
    if (l % B && (l / B + 1) * B - 1 <= r) blk[l / B].add(l % B, B - 1, k), l = (l / B + 1) * B;
    while (l + B - 1 <= r) blk[l / B].tag += k, l += B;
    if (l <= r) blk[l / B].add(l % B, r % B, k);
  }
  LL sum(int n, int k) {
    LL res = 0;
    for (int i = 0; i * B <= n; i++) res += blk[i].calc(k);
    debug("sum(%d, %d) = %lld\n", n, k, res);
    return res;
  }
  int select(int n, int k, int idx) {
    ++idx;
    for (int i = 0; i * B <= n; i++) {
      if ((i + 1) * B <= n && blk[i].count(k) < idx) idx -= blk[i].count(k);
      else return blk[i].select(k, idx) + i * B;
    }
    throw logic_error("ds::select: wrong argument");
  }
};
int n, dfn[100010], rdel[100010], ptr[100010], fa[100010], cnt, siz[100010], rnk[200010], q;
vector<int> g[100010];
void dfs(int u, int _fa) {
  if (u != 1) {
    int &pos = rdel[u] = (int)(find(g[u].begin(), g[u].end(), _fa) - g[u].begin()), c = (int)g[u].size();
    pos = (pos + 1) % c, ptr[u] = (ptr[u] - pos + c) % c;
    rotate(g[u].begin(), g[u].begin() + pos, g[u].end());
  }
  dfn[u] = ++cnt, siz[u] = 1, fa[u] = _fa, rnk[cnt] = u;
  for (int v : g[u]) if (v != _fa) dfs(v, u), siz[u] += siz[v], rnk[++cnt] = u;
}
void update_t(int u, int k) {
  int l = dfn[u] + 1, r;
  if (u > 1 && ptr[u] + 1 == (int)g[u].size()) r = dfn[u] + siz[u] * 2 - 2;
  else r = dfn[g[u][ptr[u]]] - 1;
  r = min(r, cnt);
  if (l <= r) ds::add(l, r, k);
}
int main() {
#ifndef LOCAL
  cin.tie(nullptr)->sync_with_stdio(false);
#endif
  cin >> n;
  for (int i = 1, c; i <= n; i++) {
    cin >> c >> ptr[i];
    g[i].resize(c);
    for (int j = 0; j < c; j++) cin >> g[i][j];
  }
  dfs(1, 0), --cnt;
  for (int i = 1; i <= n; i++) update_t(i, +1);
  cin >> q;
  while (q--) {
    char op;
    cin >> op;
    if (op == 'C') {
      int x, y;
      cin >> x >> y;
      update_t(x, -1);
      int c = (int)g[x].size();
      ptr[x] = (y - rdel[x] + c) % c;
      update_t(x, +1);
    } else {
      LL k;
      cin >> k;
      ++k;
      int now = 0;
      auto calc = [&](int now) { return 1ll * cnt * now - ds::sum(cnt, now); };
      for (int j = __lg(n); j >= 0; j--) if (calc(now + (1 << j)) < k) now += 1 << j;
      debug("k = %lld, calc(%d) = %lld\n", k, now, calc(now));
      if (now >= n) cout << rnk[(k - calc(now) - 1) % cnt + 1] << endl;
      else cout << rnk[ds::select(cnt, now, k - calc(now))] << endl;
    }
  }
  return 0;
}

posted @ 2024-10-18 12:11  caijianhong  阅读(31)  评论(0编辑  收藏  举报