题意:给你一棵无向树,每次修改一条边,询问树的直径。
说明:CEOI中曾经考到这样一题传送门,这题要求强制在线,而本题则是第二天会恢复的,所以最多1次修改。
思路:
这张图包含了样例1,和答案的含义。
如果说我们修改了,uv这条边,那么可能的最大直径有两种情况,一种是经过uv这条边的,那么我们需要知道从u开始向左延伸的最长路,和v开始向右延伸的最长路。我们可以维护三个数组,最长路d1,次长路d2,最长路的第一个节点,也就是最长路延伸的方向nxt,这样我们可以看看如果u最长路走v方向,那么我们就只能选次长路,如果不是我们就可以最长路,这样就能找到最远距离了。如何维护最长路,这是非常经典的问题,建议自主学习。传送门接下来考虑如果不经过uv的话,我们需要维护一个sub数组,表示,某点的子树的最大直径,sub[u],表示红色框部分的子树直径,注意v也是u的儿子,蓝色框表示sub[v],v的所有子树,两者取max,我们就可以得到u子树向左的直径与v子树向右的直径的最大值。为了换根求出sub数组,首先需要求出各个直径。建议自主学习,树形dp求直径。所以我们需要开一个数组表示定根向下的各个直径,此时我们可以求出一个虚假的sub,因为这是第一次dfs,我们只能求出某个根条件下,向下的子树。向上的部分还没有并上去。这时候我们就需要用到换根dp了。右上部分表示sub换根的过程,如果说当前我们到了u子树的话,需要更新v子树,我们已经知道了up子树的直径,我们还需要知道u子树减去v子树部分的直接,我们还需要知道跨越u节点的直径,此时我们就需要开出第d3表示次次长路,如果说最长路经过v,我们跨越u的直径必须是次长和次次长,如果说是次长,就是最长,和次次长,其他是最长和次长。up是递归dp得到是已知的。余下的两树之差的部分,我们就需要开一个次大的sub了,如果说最大的sub恰好用上的v子树,我们就需要用次大的sub,反之亦然,三者取大我们跨越得到除v子树外的直径了,这样我们就能结合v子树本身的sub_u算出真实的sub。代码中的ifelse是换根的精髓,需要细细思考。
#include <bits/stdc++.h>
#define ll long long
#define inf 0x3f3f3f3f
using namespace std;
const int N = 2e5 + 5;
struct eg {
int v, w;
};
vector<eg> g[N];
ll d[N][3], dia_u[N], sub_u[N][2];//直径,除去u的直径,向下的子树
int nxt[N], nxt1[N];//最长路的后继,次长路的后继,d[0]最长,d[1]次长
ll d1[N], d2[N], d3[N];//最长路,次长路,次次长路
ll sub1[N], sub2[N];//除去该点的子树直径,次大的子树直径
void dfs1(int u, int fa) {//预处理定根向下的数组
for (auto i : g[u]) {
int v = i.v, w = i.w;
if (v == fa) continue;
dfs1(v, u);
if (d[v][0] + w > d[u][0]) {//u最大的向下深度
nxt1[u] = nxt[u];//次大方向
nxt[u] = v;//最大方向
d3[u] = d[u][2] = d[u][1];
d2[u] = d[u][1] = d[u][0];
d1[u] = d[u][0] = d[v][0] + w;
} else if (d[v][0] + w > d[u][1]) {//次大
nxt1[u] = v;
d3[u] = d[u][2] = d[u][1];
d2[u] = d[u][1] = d[v][0] + w;
} else if (d[v][0] + w > d[u][2]) {
d3[u] = d[u][2] = d[v][0] + w;
}
dia_u[u] = max({dia_u[v], dia_u[u], d[u][0] + d[u][1]});//更新u子树直径
if (dia_u[v] > sub_u[u][0]) {
sub2[u] = sub_u[u][1] = sub_u[u][0];
sub1[u] = sub_u[u][0] = dia_u[v];
} else if (dia_u[v] > sub_u[u][1]) {
sub2[u] = sub_u[u][1] = dia_u[v];
}
}
}
void dfs2(int u, int fa, ll up) {//向上生长的部分合并上去
if (up > d1[u]) {
nxt1[u] = nxt[u];
nxt[u] = fa;
d3[u] = d2[u];
d2[u] = d1[u];
d1[u] = up;
} else if (up > d2[u]) {
nxt1[u] = fa;
d3[u] = d2[u];
d2[u] = up;
} else if (up > d3[u]) {
d3[u] = up;
}
for (auto i : g[u]) {
int v = i.v, w = i.w;
if (v == fa) continue;
if (d[v][0] + w == d[u][0]) dfs2(v, u, max(d[u][1], up) + w);//用上子树v的话
else dfs2(v, u, max(d[u][0], up) + w);//没用上的话
}
}
void dfs3(int u, int fa, ll up) {
if (up > sub1[u]) {
sub2[u] = sub1[u];
sub1[u] = up;
} else if (up > sub2[u]) {
sub2[u] = up;
}
for (auto i : g[u]) {
int v = i.v, w = i.w;
if (v == fa) continue;
ll upp;
if (nxt[u] == v) upp = d2[u] + d3[u];//如果经过u的最长路不能用的话
else if (nxt1[u] == v) upp = d1[u] + d3[u];//如果经过u的次长路不能用的话
else upp = d1[u] + d2[u];
if (dia_u[v] == sub_u[u][0]) dfs3(v, u, max({sub_u[u][1], up, upp}));//如果v子树在sub[u]内的话
else dfs3(v, u, max({sub_u[u][0], up, upp}));
}
}
void solve() {
int n;
cin >> n;
for (int i = 1; i < n; i++) {
int u, v, w;
cin >> u >> v >> w;
g[u].push_back({v, w});
g[v].push_back({u, w});
}
dfs1(1, 0);
dfs2(1, 0, 0);
dfs3(1, 0, 0);
int m;
cin >> m;
for (int i = 0; i < m; i++) {
int u, v, w;
cin >> u >> v >> w;
ll du, dv;
du = (nxt[u] == v ? d2[u] : d1[u]);
dv = (nxt[v] == u ? d2[v] : d1[v]);
cout << max({du + w + dv, sub1[u], sub1[v]}) << '\n';//经过uv或者不经过uv的话
}
}
int main() {
ios::sync_with_stdio(false);
cin.tie(0);
int T = 1;
// cin >> T;
while (T--) {
solve();
}
return 0;
}