CF1192B Dynamic Diameter 题解
马上省选了不会欧拉序,来补补。
首先看到动态维护直径比较容易想到的肯定还是 LCT。常见套路是两个连通块合并后的两直径端点必然是原来两个连通块 个直径端点中的其二。证明显然。新直径要么是原来两个连通块直径,要么经过这条新的边。又一个点到树上最远距离的点一定是直径端点之一,结论必然成立。
于是我们容易搞出 LCT 的只加边维护直径的方法,删除可以离线套线段树分治变成 ,但是这题强制在线。所以用 LCT 似乎要 DDP。然而这东西难写难调,考虑一个容易点的做法。
下面提供两个做法。
做法
考虑还是用上面这个套路,令根为 号节点,用线段树维护 DFS 序,每个区间维护下这段 DFS 的直径端点。合并的时候分类讨论几种,取路径 。现在难点在于边权修改。考虑修改一条边对线段树的影响。显然只有经过这条边的路径才会受到边权的改变。令这条边中深度更大的那个为 ,以 为根的子树对应 DFS 区间为 。线段树上只有和这个区间有交但不被这个区间完全包含的区间才会进行更改。事实上这些区间就是我们进行区间查询这个区间找到的所有最后没有直接返回的区间。这个区间量级显然是 的。然后进行修改,每次只对这些区间修改并往上合并,就做到了 。口胡的,没写代码。
做法
考虑不用上面的合并套路。直径的本质是什么?如果以一个点(比如 号点)为根,那么直径就是 ,也就是任意两点路径的最大值。
考虑欧拉序,即进行深度优先搜索,到达一个点或者从其儿子回溯到这个点,都将其加到序列后面。最终形成一个长度为 的序列,设点 第一次出现位置 ,最后一次出现位置 。容易知道 的最近公共祖先是 中深度最小的点。于是我们知道直径就是 。用线段树维护序列并且对每个区间记录 的最大最小值以及 对于 和 分别的最大值即可容易的合并区间。修改边权转成区间加,即对于 区间加,上述标记都可以 更新。复杂度 。
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cmath>
#include <functional>
#include <cstring>
#include <string>
#include <vector>
#include <array>
#include <queue>
using namespace std;
using ll = long long;
const int N = 2e5 + 5;
int n, q;
ll maxw;
ll lastans;
ll dis[N], nd[N];
array<int, N> dep;
vector<pair<int, ll>> G[N];
array<int, N> euler;
array<int, N> U, V;
array<ll, N> W;
int idx;
array<int, N> fir, lst;
class SegmentTree
{
public:
struct Node
{
int l, r;
ll tag, minn, maxn, res;
ll r1, r2;
friend Node operator+(const Node& a, const Node& b)
{
if (a.l == -1) return b;
if (b.l == -1) return a;
Node c;
c.l = a.l, c.r = b.r;
c.tag = 0ll;
c.minn = min(a.minn, b.minn);
c.maxn = max(a.maxn, b.maxn);
c.res = max({ a.res, b.res, a.r2 + b.maxn, a.maxn + b.r1 });
c.r1 = max({ a.r1, b.r1, b.maxn - 2ll * a.minn });
c.r2 = max({ a.r2, b.r2, a.maxn - 2ll * b.minn });
return c;
}
}tr[N << 2];
void pushtag(int u, ll v)
{
tr[u].tag += v;
tr[u].maxn += v;
tr[u].minn += v;
tr[u].r1 -= v;
tr[u].r2 -= v;
}
function<void(int)> pushdown = [&](int u)
{
if (tr[u].tag)
{
pushtag(u << 1, tr[u].tag);
pushtag(u << 1 | 1, tr[u].tag);
tr[u].tag = 0;
}
};
function<void(int, int, int, ll*)> build = [&](int u, int l, int r, ll* v)
{
tr[u] = { l, r, 0ll, v[r], v[r], 0ll, -v[r], -v[r] };
if (l == r) return;
int mid = l + r >> 1;
build(u << 1, l, mid, v);
build(u << 1 | 1, mid + 1, r, v);
tr[u] = tr[u << 1] + tr[u << 1 | 1];
};
function<void(int, int, int, ll)> update = [&](int u, int l, int r, ll v)
{
if (tr[u].l >= l and tr[u].r <= r)
{
pushtag(u, v);
return;
}
pushdown(u);
int mid = tr[u].l + tr[u].r >> 1;
if (l <= mid) update(u << 1, l, r, v);
if (r > mid) update(u << 1 | 1, l, r, v);
tr[u] = tr[u << 1] + tr[u << 1 | 1];
};
}sgt;
auto main() -> int
{
ios::sync_with_stdio(0), cin.tie(0);
cin >> n >> q >> maxw;
for (int i = 1; i < n; i++)
{
int u, v;
ll w;
cin >> u >> v >> w;
G[u].emplace_back(make_pair(v, w));
G[v].emplace_back(make_pair(u, w));
U[i] = u, V[i] = v, W[i] = w;
}
auto dfs = [&](auto self, int u, int fa) -> void
{
euler[++idx] = u;
if (!fir[u]) fir[u] = idx;
lst[u] = idx;
dep[u] = dep[fa] + 1;
for (auto& [j, w] : G[u])
{
if (j != fa) [[likely]]
{
dis[j] = dis[u] + w;
self(self, j, u);
euler[++idx] = u;
lst[u] = idx;
}
}
};
dfs(dfs, 1, 1);
for (int i = 1; i <= 2 * n - 1; i++)
{
nd[i] = dis[euler[i]];
}
sgt.build(1, 1, 2 * n - 1, nd);
for (int i = 1; i <= q; i++)
{
ll d, e;
cin >> d >> e;
d = (d + lastans) % (n - 1);
e = (e + lastans) % maxw;
d++;
int u = (dep[U[d]] < dep[V[d]] ? V[d] : U[d]);
sgt.update(1, fir[u], lst[u], -W[d] + e);
W[d] = e;
cout << (lastans = sgt.tr[1].res) << "\n";
}
return 0;
}

浙公网安备 33010602011771号