CF1783G 题解

· · 题解

题面

给定一棵 n 个点,第 i 个点有点权 a_i 的树,对于一个点 u 定义偏心距 e(u) = \max\{ dis(u, v) + a_v\},其中 dis(u, v) 表示两点在树上的距离。定义半径 r = \min\{e(u)\}

$1 \leqslant n \leqslant 2 \times 10^5, 1 \leqslant m \leqslant 10^5$。 #### 题解 首先发现半径不是很好做,但是直径看上去就很有对称性,因此我们先考虑求直径的问题,也就是 $nd(u, v) = dis(u, v) + a_u + a_v, d = \max\{nd(x, y)\}$。 这个 $d$ 就相当于,在原树的基础上,每个点附带了两个长度为 $a_i$ 的链(这里挂两条的意义是要满足 $nd(u, u) = 2a_u$),得到的新树的直径,这种在一个正常的树上的直径往往能带来很多优秀的性质。 分析 $d$ 和 $r$ 之间的关系,考虑新树的直径中点,如果这个中点 $mid$ 在原树上,根据直径中点的性质有 $r = e(mid) = \lceil \frac{d}{2} \rceil$。否则我们就取不到这个 $mid$,那此时考虑这一条直径对应的原树上的路径 $(u, v)$,也就是 $d = nd(u, v)$,不妨设 $mid$ 在 $u$ 所对应的链上,则有 $e(u) = a_u$,而对于任何一个其他的点 $x$, 有 $e(x) \geqslant dis(x, u) + a_u > a_u = e(u)$,因此这种情况下 $r = e(u)$。另外我写完代码才发现第二种情况不可能发生,因为如果是这样会有 $d = nd(u, u)$。 通过刚才的分析,我们发现只要能维护新树的直径,以及这个直径对应的原树上的端点,我们就能轻松解决这个问题。 一个众所周知的结论是,如果用一条边将两棵树连接,则新直径端点一定都来源于旧的四个直径端点。实际上有一个更强的结论:在同一棵树上的两个虚树取并,新的直径端点同样来源于旧的四个直径端点。 简单证明一下,如果新直径端点都来源于旧的同一棵虚树,则结论显然成立。 如果来源于不同的虚树,那么假设新直径 $(u, v)$ 不满足上述结论,它们分别来自原虚树 $A$ 和 $B$。则路径 $(u, v)$ 一定是由一段来自于 $A$ 的路径与一段来自于 $B$ 的路径并起来的,不然图就不是树了。 此时考虑距离 $v$ 最远的在路径 $(u, v)$ 上且在虚树 $B$ 里面的点 $x$,那么原本虚树 $B$ 的两个直径端点 $a, b$ 一定有一个距离 $x$ 不少于 $v$ 到 $x$ 的距离,而且路径不会经过 $(u, x)$(这条路径完全不在 $B$ 里面)。$u$ 也可以以同样的方式调整。因此发现 $(u, v)$ 根本不是直径,出现矛盾,假设不成立。 有了这个结论,我们就可以以任意顺序合并每个点,并且需要存下来的信息量是非常少的,可以直接用线段树维护,一个节点存储编号在 $[l, r]$ 内的点(以及它们带着的链)组成的虚树的直径长度及其端点。 每一次合并需要 $O(1)$ 次求树上两点距离,这可以通过 $O(1)$ 或 $O(\log n)$ 的求 lca 来实现。 根据求 lca 方式的不同,最终复杂度是 $O(n \log n)$ 或者 $O(n \log^2 n)$,我这里使用的是 $O(n \log n) - O(1)$ 求 lca。 #### 代码 ```cpp #include <cstdio> #include <cstring> #include <algorithm> #define FILEIO(filename) (freopen(filename ".in", "r", stdin), freopen(filename ".out", "w", stdout)) typedef long long ll; typedef unsigned long long ull; const int N = 200005; int n, m, val[N]; struct Edge { int to, nxt; } E[N << 1]; int head[N], tot; inline void add(int f, int t) { E[++tot] = {t, head[f]}, head[f] = tot; return; } int dep[N], dfn[N], tt, lg[N << 1], euler[N << 1], st[N << 1][20]; void dfs(int x, int from) { dep[x] = dep[from] + 1, euler[dfn[x] = ++tt] = x; for (int i = head[x]; i;i = E[i].nxt) { int y = E[i].to; if(y == from) continue; dfs(y, x), euler[++tt] = x; } return; } inline int depmin(int x, int y) { return dep[x] <= dep[y] ? x : y; } inline void st_init(void) { for (int i = 1; i <= tt;++i) lg[i] = lg[i >> 1] + 1; for (int i = 1; i <= tt;++i) st[i][0] = euler[i]; for (int k = 1; k < lg[tt];++k) for (int i = 1; i + (1 << k) - 1 <= tt;++i) st[i][k] = depmin(st[i][k - 1], st[i + (1 << (k - 1))][k - 1]); return; } inline int query(int l, int r) { int k = lg[r - l + 1] - 1; return depmin(st[l][k], st[r - (1 << k) + 1][k]); } inline int lca(int x, int y) { x = dfn[x], y = dfn[y]; if(x > y) std::swap(x, y); return query(x, y); } inline ll dis(int x, int y) { int f = lca(x, y); return dep[x] + dep[y] - 2 * dep[f]; } struct Info { int x, y; ll len; Info(void) { x = y = len = 0; return; } Info(int _x, int _y) { x = _x, y = _y, len = dis(x, y) + val[x] + val[y]; return; } inline bool operator<(Info b) const { return len < b.len; } }; inline Info merge(Info a, Info b) { Info p[6] = {a, b, Info(a.x, b.x), Info(a.x, b.y), Info(a.y, b.x), Info(a.y, b.y)}; return *std::max_element(p, p + 6); } struct SgT { Info t[N << 2]; inline int ls(int x) { return x << 1; } inline int rs(int x) { return x << 1 | 1; } inline void pushup(int x) { t[x] = merge(t[ls(x)], t[rs(x)]); return; } void build(int x, int l, int r) { if(l == r) return t[x] = Info(l, l), void(0); int mid = (l + r) >> 1; build(ls(x), l, mid), build(rs(x), mid + 1, r); pushup(x); return; } void update(int x, int l, int r, int p) { if(l == r) return t[x] = Info(p, p), void(0); int mid = (l + r) >> 1; if(p <= mid) update(ls(x), l, mid, p); else update(rs(x), mid + 1, r, p); pushup(x); return; } inline ll query(void) { return std::max<ll>((t[1].len + 1) / 2, std::max(val[t[1].x], val[t[1].y])); } } T; int main(void) { scanf("%d", &n); for (int i = 1;i <= n;++i) scanf("%d", val + i); for (int i = 1, u, v; i < n;++i) scanf("%d%d", &u, &v), add(u, v), add(v, u); dfs(1, 0), st_init(), T.build(1, 1, n); scanf("%d", &m); for (int i = 1, x, y; i <= m;++i) { scanf("%d%d", &x, &y); val[x] = y; T.update(1, 1, n, x); printf("%lld\n", T.query()); } return 0; } ```