第十五届吉林省赛【D. Rush Morning】【换根DP/欧拉序+线段树】

题意:给你一棵无向树,每次修改一条边,询问树的直径。
说明: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; 
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值