CF1192B Dynamic Diameter 题解

思路#

静态 top tree 板子题。

定义#

我们使用簇来表示树上的一个连通块。

可以按照如下方式定义一个簇:

  1. 一个簇可以表示为三元组 (u,v,E),其中 u,v 为树的节点,称为簇的界点,E 为一个边的集合,表示该簇包含的边,路径 (u,v) 称作簇路径。
  2. u,v 分别为上界点与下界点,上界点是一个簇内深度最小的点,下界点则是除去上界点外的界点。
  3. 界点可以通俗的理解为簇与簇的分界点。
  4. 对于树的一条边 (u,v)(u,v,(u,v)) 是一个簇。
  5. 对于簇 (u,v,E1) 与簇 (v,w,E2)E1E2=,那么 (u,w,E1E2) 也是一个簇,这个操作称为 compress
  6. 对于簇 (x,v,E1) 与簇 (x,w,E2)E1E2=,那么 (x,w,E1E2) (或者 (x,v,E1E2))也是一个簇,这个操作称为 rake

经典图:

一个感性理解是 rake 将簇与簇融合,compress 将簇与簇拼接。

这两个操作可以帮助我们将整棵树合并为一个簇。

  • 对于一度点,进行 rake
  • 对于二度点,进行 compress

收缩过程中,簇的合并结构形成了一个树,我们把这个树称为 top tree

现在,这样一个 top tree 它的树高有可能是 O(n) 的。

更优的树#

考虑构造一个树高更小的 top tree

容易想到的是全局平衡二叉树。

全局平衡二叉树提供了一个分治方案使整棵树的全局平衡二叉树树高为 O(logn)

我们同样可以把这个分治方案放在 top tree 上。

具体的,将树进行重链剖分。

然后对于轻儿子,先把它们 rake 起来。

再对重链分治 compress,找分割点时,我们按每个点合并完轻儿子后的簇的大小带权找到中点。

这样就可以建出一颗树高为 O(logn)top tree 了。

关于这道题#

建出 top tree 后有什么用呢。

我们想线段树一样的操作它

对于动态直径。

我们把每一个簇,维护三个值。

簇内部的最长距离及到两个界点的最长距离。

合并时我们对两种操作分类讨论拼接起来即可。

可以发现,维护直径的方式与线段树维护最大子段和的方式是比较类似的。

这种维护方法就可以支持一些简单的修改,比如此题的修改边权。

时间复杂度:O(nlogn)

Code#

/*
  ! 如果没有天赋,那就一直重复
  ! Created: 2024/06/05 19:58:38
*/
#include <bits/stdc++.h>
using namespace std;

#define int long long
#define fro(i, x, y) for (int i = (x); i <= (y); i++)
#define pre(i, x, y) for (int i = (x); i >= (y); i--)

const int N = 2e5 + 10;

int n, q, w, ct, last, head[N];
int sz[N], sn[N], fa[N], rt[N], dep[N];
struct Node {
  int u, v, w;
} d[N];
struct edge {
  int to, nxt;
} e[N << 1];
struct node {
  enum { UNIT, RAKE, COMP } type;
  int u, v, sz, ls, rs, fa, ln, mi, un, vn;
} t[N << 1];

inline void add(int x, int y) {
  e[++ct] = {y, head[x]}, head[x] = ct;
  e[++ct] = {x, head[y]}, head[y] = ct;
}
inline void dfs(int now, int fa) {
  sz[now] = 1, dep[now] = dep[fa] + 1;
  for (int i = head[now]; i; i = e[i].nxt) {
    int x = e[i].to;
    if (x == fa) continue;
    dfs(x, now);
    t[x] = {node::UNIT, now, x, 1};
    sz[now] += sz[x];
    if (sz[x] > sz[sn[now]]) sn[now] = x;
  }
}
inline void pup(int p) {
  int x = t[p].ls, y = t[p].rs;
  if (t[p].type == node::RAKE) {
    t[p].ln = t[x].ln;
    t[p].mi = max({t[x].mi, t[y].mi, t[x].un + t[y].un});
    t[p].un = max(t[x].un, t[y].un);
    t[p].vn = max(t[x].vn, t[x].ln + t[y].un);
  } else if (t[p].type == node::COMP) {
    t[p].ln = t[x].ln + t[y].ln;
    t[p].mi = max({t[x].mi, t[y].mi, t[x].vn + t[y].un});
    t[p].un = max(t[x].un, t[x].ln + t[y].un);
    t[p].vn = max(t[y].vn, t[y].ln + t[x].vn);
  }
}
inline auto rake(int x, int y) {
  assert(t[x].u == t[y].u);
  return t[++ct] = {node::RAKE, t[x].u, t[x].v, t[x].sz + t[y].sz, x, y}, t[x].fa = t[y].fa = ct, pup(ct), ct;
}
inline auto comp(int x, int y) {
  assert(t[x].v == t[y].u);
  return t[++ct] = {node::COMP, t[x].u, t[y].v, t[x].sz + t[y].sz, x, y}, t[x].fa = t[y].fa = ct, pup(ct), ct;
}
template<typename T, typename Func>
inline int build(T l, T r, Func f) {
  if (r == l) return 0;
  if (r == l + 1) return *l;
  int all = 0, sum = 0;
  for (auto it = l; it != r; it++) all += t[*it].sz;
  T mid = l + 1;
  for (auto it = l; it != r; it++) {
    sum += t[*it].sz;
    if (sum <= all / 2) mid = it + 1; else break;
  }
  return f(build(l, mid, f), build(mid, r, f));
}
inline void sol(int now, int ff, bool f) {
  fa[now] = ff;
  if (sn[now]) sol(sn[now], now, false);
  for (int i = head[now]; i; i = e[i].nxt)
    if (e[i].to != sn[now] && e[i].to != fa[now]) sol(e[i].to, now, true);
  if (f) {
    vector<int> s1;
    if (ff) s1.push_back(now);
    for (int i = sn[now]; i; i = sn[i]) {
      vector<int> s2{i};
      for (int j = head[fa[i]]; j; j = e[j].nxt)
        if (e[j].to != i && e[j].to != fa[fa[i]]) s2.push_back(rt[e[j].to]);
      s1.push_back(build(s2.begin(), s2.end(), rake));
    }
    rt[now] = build(s1.begin(), s1.end(), comp);
  }
}
inline void upd(int x, int w) {
  t[x].un = t[x].vn = t[x].mi = t[x].ln = w;
  while (t[x].fa) {
    pup(t[x].fa), x = t[x].fa;
  }
}

signed main() {
  ios::sync_with_stdio(0), cin.tie(0);
  cin >> n >> q >> w;
  fro(i, 1, n - 1) {
    cin >> d[i].u >> d[i].v >> d[i].w;
    add(d[i].u, d[i].v);
  }
  ct = n;
  dfs(1, 0);
  sol(1, 0, 1);
  fro(i, 1, n - 1) {
    if (dep[d[i].u] > dep[d[i].v])
      swap(d[i].u, d[i].v);
    upd(d[i].v, d[i].w);
  }
  fro(i, 1, q) {
    int x, y;
    cin >> x >> y;
    x = (x + last) % (n - 1) + 1;
    y = (y + last) % (w);
    d[x].w = y;
    upd(d[x].v, d[x].w);
    cout << (last = t[rt[1]].mi) << "\n";
  }
  return 0;
}

作者:JiaY19

出处:https://www.cnblogs.com/JiaY19/p/18238313

版权:本作品采用「署名-非商业性使用-相同方式共享 4.0 国际」许可协议进行许可。

posted @   JiaY19  阅读(17)  评论(0编辑  收藏  举报
编辑推荐:
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· 写一个简单的SQL生成工具
· AI 智能体引爆开源社区「GitHub 热点速览」
· C#/.NET/.NET Core技术前沿周刊 | 第 29 期(2025年3.1-3.9)
more_horiz
keyboard_arrow_up dark_mode palette
选择主题
menu
点击右上角即可分享
微信分享提示