CF207C3 Game with Two Trees 题解

脑子不够,科技来凑。

不过好像也没有用多么高级的科技……

首先这个题目很坏,它让你翻转 St2。即从 t2 某个节点往下走到另一个节点的路径所表示的字符串。这个非常难以维护。因此我们不翻转 St2,转而翻转 St1。两者的效果是相同的。于是询问就变成了从 t1 的根走到某个节点的路径,以及从 t2 某个节点向上走的路径,两者相等的情况数。

由于节点只加不删,考虑算出加入每个节点会产生的贡献,最后再滚一遍前缀和得到答案。由于 t2 询问的是从某个节点向上的路径。考虑离线建出两棵树并把边权下放到点,对 t2 进行树上后缀排序。按深度从小到大处理每个 t1 节点 i。显然 i 能够匹配到的 t2 的 SA 区间只会比其父亲更小。在其父亲能够匹配到的 SA 区间内二分。由于 i 所代表的字符串长度只比其父亲多 1,所以在二分时只需要判断 SAmid 的第 k 级祖先是否与 i 的字符相等即可(k=depidep1=1depi=depfai+1)。这样就可以得到每个 t1 节点 i 能够匹配到的 t2 的 SA 区间。记作 [Li,Ri)

现在来具体分析一下插入一个节点时会产生的贡献。

当插入一个 t1 节点时:

  1. 每个之前插入的能与其匹配的 t2 节点会对答案产生贡献。因此要记录 rec1i 表示 SAi 有无被插入。这部分是对 rec1 进行一个区间求和。
  2. 该点将与未来每个能与其匹配的 t2 节点产生贡献。考虑记录 rec2i 表示每个 t2 节点 i 能与多少个 t1 节点匹配。这部分是对 rec2 进行一次区间加 1。

当插入一个 t2 节点时:

  1. 每个之前插入的能与其匹配的 t1 节点会对答案产生贡献。这部分是对 rec2 进行一个单点求值。
  2. 该点将与未来每个能与其匹配的 t1 节点产生贡献。这部分是对 rec1 进行一次单点加 1。

以上操作均能用树状数组简单地维护。于是问题只剩下如何进行树上后缀排序了。因为我没有脑子,所以我直接用后缀平衡树了。感觉没那么难写。

若使用长剖求树上 k 级祖先,则复杂度 O(nlogn)。然而长剖常数太大,实际跑起来不如重剖。因此实现时使用了朴素的重剖求树上 k 级祖先。复杂度 O(nlog2n)。常数不大。

Code:

constexpr int maxn = 100010;
int faA[maxn], faB[maxn], SA[maxn], Rank[maxn], *_p = SA, cntA, cntB, op[maxn];

typedef long long i64;
typedef unsigned long long u64;
constexpr u64 INF = 1ull << 62;
#define isNull(x) (x->son[0] == x)
char sA[maxn], sB[maxn];

struct Suffix_n {
  constexpr static double ALPHA = 0.75;
  Suffix_n *son[2];
  int id, size;
  u64 val;

  inline void pushup() {
    if (isNull(this))
      return;
    size = son[0]->size + son[1]->size + 1;
  }
  inline bool need_rebuild() {
    return ALPHA * size <= max(son[0]->size, son[1]->size);
  }
} *rec[maxn];

struct Suffix_t {
  constexpr static int SIZE = maxn;
  Suffix_n MemoryPool[SIZE], *const null = MemoryPool, *root = null, *pointer = null;
  Suffix_t() {
    rec[0] = null->son[0] = null->son[1] = null;
  }
  inline bool cmp(int a, int b) {
    return sB[a] ^ sB[b] ? sB[a] < sB[b] : rec[faB[a]]->val < rec[faB[b]]->val;
  }
  inline Suffix_n *allocate(int id, u64 num) {
    return *++pointer = {null, null, id, 1, num}, pointer;
  }
  vector<Suffix_n *> _need;
  void pre_rebuild(Suffix_n *r) {
    (r->son[0] != null) && (pre_rebuild(r->son[0]), 0), _need.emplace_back(r),
        (r->son[1] != null) && (pre_rebuild(r->son[1]), 0);
  }
  Suffix_n *rebuild_main(int left, int right, u64 L, u64 R) {
    if (left > right)
      return null;
    int mid     = (left + right) >> 1;
    u64 vmid    = (L + R) >> 1;
    Suffix_n *r = _need[mid];
    r->val      = vmid;
    r->son[0] = rebuild_main(left, mid - 1, L, vmid), r->son[1] = rebuild_main(mid + 1, right, vmid, R);
    r->pushup();
    return r;
  }
  inline void rebuild(Suffix_n *&r, u64 L, u64 R) {
    _need.clear(), pre_rebuild(r), r = rebuild_main(0, _need.size() - 1, L, R);
  }
  void insert(Suffix_n *&r, int id, u64 L = 0, u64 R = INF) {
    if (r == null) {
      rec[id] = r = allocate(id, (L + R) >> 1);
      return;
    }
    cmp(id, r->id) ? insert(r->son[0], id, L, r->val) : insert(r->son[1], id, r->val, R);
    r->pushup();
    if (r->need_rebuild())
      rebuild(r, L, R);
  }
  inline void dfs(Suffix_n *r) {
    (r->son[0] != null) && (dfs(r->son[0]), 0), *++_p = r->id, (r->son[1] != null) && (dfs(r->son[1]), 0);
  }
} Suffix;

basic_string<int> edge[maxn];
int size[maxn], fa[maxn], dep[maxn], son[maxn], top[maxn], dfn[maxn], MAP[maxn], _id, root;
void dfs1(int u, int _fa) {
  size[u] = 1, fa[u] = _fa, dep[u] = dep[_fa] + 1;
  for (const int &v : edge[u])
    dfs1(v, u), size[u] += size[v], (size[v] > size[son[u]]) && (son[u] = v);
}
void dfs2(int u, int _top) {
  dfn[u] = ++_id, MAP[_id] = u, top[u] = _top;
  if (!son[u])
    return;
  dfs2(son[u], _top);
  for (const int &v : edge[u])
    (v != son[u]) && (dfs2(v, v), 0);
}
inline char kthF(int x, int k) {
  if (dep[x] <= k)
    return 0;
  int tmp;
  while (k) {
    tmp = dep[x] - dep[top[x]] + 1;
    if (tmp <= k)
      x = fa[top[x]], k -= tmp;
    else
      return sB[MAP[dfn[x] - k]];
  }
  return sB[x];
}

int L[maxn], R[maxn];
inline void build() {
  for (int i = 1; i <= cntB; i++)
    Rank[SA[i]] = i;
  static int _dep[maxn];
  L[1] = 1, R[1] = cntB + 1, _dep[1] = -1;
  for (int i = 2; i <= cntA; i++) {
    _dep[i]  = _dep[faA[i]] + 1;
    int left = L[faA[i]], right = R[faA[i]], mid;
    while (left < right)
      mid = (left + right) >> 1, (kthF(SA[mid], _dep[i]) >= sA[i]) ? right = mid : left = mid + 1;
    L[i] = left, right = R[faA[i]];
    while (left < right)
      mid = (left + right) >> 1, (kthF(SA[mid], _dep[i]) > sA[i]) ? right = mid : left = mid + 1;
    R[i] = left;
  }
}

namespace BIT1 {
#define lowbit(x) (x & -x)
i64 tree[maxn];
inline void update(int id, int val) {
  for (int i = id; i <= cntB; i += lowbit(i))
    tree[i] += val;
}
inline i64 query(int l, int r) {
  l--, r--;
  i64 res = 0;
  for (int i = r; i; i -= lowbit(i))
    res += tree[i];
  for (int i = l; i; i -= lowbit(i))
    res -= tree[i];
  return res;
}
}  // namespace BIT1
namespace BIT2 {
i64 tree[maxn];
inline void update(int l, int r, int val) {
  for (int i = l; i <= cntB; i += lowbit(i))
    tree[i] += val;
  for (int i = r; i <= cntB; i += lowbit(i))
    tree[i] -= val;
}
inline i64 query(int id) {
  i64 res = 0;
  for (int i = id; i; i -= lowbit(i))
    res += tree[i];
  return res;
}
}  // namespace BIT2
i64 ANS[maxn];

int main() {
  int q;
  in(q);
  op[1] = 1, op[2] = 2, cntA = cntB = 1, q += 2;
  for (int i = 3; i <= q; i++)
    in(op[i]), (op[i] == 1) ? (cntA++, in(faA[cntA], sA[cntA])) : (cntB++, in(faB[cntB], sB[cntB]));

  for (int i = 1; i <= cntB; i++)
    Suffix.insert(Suffix.root, i), edge[faB[i]] += i;
  Suffix.dfs(Suffix.root), dfs1(1, 0), dfs2(1, 1);
  build();

  for (int i = 1, p1 = 0, p2 = 0; i <= q; i++)
    if (op[i] == 1)
      p1++, ANS[i] = BIT1::query(L[p1], R[p1]), BIT2::update(L[p1], R[p1], 1);
    else
      p2++, ANS[i] = BIT2::query(Rank[p2]), BIT1::update(Rank[p2], 1);
  for (int i = 2; i <= q; i++)
    ANS[i] += ANS[i - 1];
  for (int i = 3; i <= q; i++)
    out(ANS[i]), enter;
}
posted @   MeteorFlower  阅读(36)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示