1 条题解
-
0
自动搬运
来自洛谷,原作者为

0x3F
Wir müssen wissen, wir werden wissen.搬运于
2025-08-24 22:48:42,当前版本为作者最后更新于2023-06-28 10:28:11,作者可能在搬运后再次修改,您可在原文处查看最新版自动搬运只会搬运当前题目点赞数最高的题解,您可前往洛谷题解查看更多
以下是正文
假如没有地铁,那么每条边对答案的贡献为 ,其中 为这条边把树分成的两部分分别的点权和,如图。

现在有了地铁,使得答案减少了 。
假设一个人从 到 ,其中 共 个点, 条边与地铁重合( 可以为 ),那么对 的贡献为:
$$(w_{a\to p_1}-w'_{a\to p_1})+(w_{p_1\to p_2}-w'_{p_1\to p_2})+\cdots+(w_{p_k\to b}-w'_{p_k\to b})-t $$其中 表示从 到 的边的 的值。
上式也可以改写为:
$$(w_{a\to p_1}-w'_{a\to p_1}-t)+t+(w_{p_1\to p_2}-w'_{p_1\to p_2}-t)+t+\cdots+t+(w_{p_k\to b}-w'_{p_k\to b}-t) $$也就是说,在这个人移动的过程中,经过的地铁的每条边对 产生的贡献为 ,每个“内点”(不是 的点)对 产生的贡献为 ,如图。

考虑每条边和每个点产生贡献的次数。
每条边产生贡献,当且仅当它被经过。因此次数等于 ,其中 为这条边把树分成的两部分分别的点权和。
每个点产生贡献,当且仅当它作为“内点”被经过。由于地铁是一条链,在链上,与之相邻的两条边均被经过。因此次数等与 ,其中 为这两条边把树分成的三部分中,不包含该点的两部分分别的点权和。端点的贡献为 。
因此对 的贡献分别为 和 ,如图。

可以使用树形 DP 求出 的最大值。
如果树根为 ,记 表示如果链的一端为 的父亲,另一端在 的子树内,那么这条链产生的贡献,最大是多少。
转移有两种可能性:
一、另一端就是 。此时只有 连向父亲的边产生贡献。
即 $dp_{i}\gets(w_{i\to f_{i}}-w'_{i\to f_{i}}-t)siz_{i}(N-siz_{i})$。
其中 表示 的父亲, 表示 的子树内点权之和, 表示所有点权之和。
二、另一端不是 。假设另一端在 的子树内,则不仅 连向父亲的边产生贡献,而且 从端点变为内点,产生贡献。
即 $dp_{i}\gets(w_{i\to f_{i}}-w'_{i\to f_{i}}-t)siz_{i}(N-siz_{i})+tsiz_{j}(N-siz_i)+dp_j$。
如图。

假设 是地铁中深度最浅的点。
如果 是端点,与 相邻的是 (是 的儿子),那么 。
如果 不是端点,与 相邻的是 (都是 的儿子),由于点 是内点,产生贡献,那么 。
如图。

如果暴力枚举 ,会超时,应当使用斜率优化。
如果有 相同的,那么选取 最大和次大的作为 ,计入答案。
然后将 去重,只保留 最大的,将 从小到大排序。
由 ,移项得:
$$\underset{y}{\underline{dp_s}}=\underset{k}{\underline{-tsiz_t}}\times\underset{x}{\underline{siz_s}}+\underset{b}{\underline{D-dp_t}} $$由于 均单调,可以使用单调队列维护凸包。
时间复杂度为 。
代码:
#include <bits/stdc++.h> using namespace std; const int _ = 1e5 + 10; const int __ = 2e5 + 10; int id, n, t, e, hd[_], nx[__], to[__], ln1[__], ln2[__]; long long siz[_], N; __int128 dp[_]; inline void add(int u, int v, int w1, int w2) { e++; nx[e] = hd[u]; to[e] = v; ln1[e] = w1; ln2[e] = w2; hd[u] = e; } __int128 sum, dif; void dfs1(int x, int f) { for (int i = hd[x]; i; i = nx[i]) { int y = to[i]; if (y != f) { dfs1(y, x); siz[x] += siz[y]; sum += __int128(siz[y]) * (N - siz[y]) * (ln1[i]); } } } int m; struct node { __int128 x; __int128 y; } arr[_]; int l, r, q[_]; inline bool cmp(node a, node b) { if (a.x == b.x) return (a.y > b.y); return (a.x < b.x); } inline bool eqn(node a, node b) { return (a.x == b.x); } inline __float128 slope(node a, node b) { return ((__float128)(b.y - a.y) / (__float128)(b.x - a.x)); } void dfs2(int x, int f, __int128 z) { dp[x] = z; for (int i = hd[x]; i; i = nx[i]) { int y = to[i]; if (y != f) { dfs2(y, x, __int128(siz[y]) * (N - siz[y]) * (ln1[i] - ln2[i] - t)); if (f) dp[x] = max(dp[x], dp[y] + z + __int128(siz[y]) * (N - siz[x]) * (t)); dif = max(dif, dp[y]); } } m = 0; for (int i = hd[x]; i; i = nx[i]) { int y = to[i]; if (y != f) { m++; arr[m].x = siz[y]; arr[m].y = dp[y]; } } sort(arr+1, arr+m+1, cmp); for (int i = 1; i < m; i++) { if (arr[i].x == arr[i+1].x) { dif = max(dif, arr[i].y + arr[i+1].y + t * arr[i].x * arr[i+1].x); } } m = unique(arr+1, arr+m+1, eqn) - arr - 1; l = r = 1; q[1] = 1; for (int i = 2; i <= m; i++) { while (r > l && slope(arr[q[l]], arr[q[l+1]]) > (-t * arr[i].x)) l++; dif = max(dif, arr[i].y + arr[q[l]].y + t * arr[i].x * arr[q[l]].x); while (r > l && slope(arr[q[r]], arr[i]) > slope(arr[q[r-1]], arr[i])) r--; q[++r] = i; } } int main() { cin >> id >> n >> t; for (int i = 1; i <= n; i++) { cin >> siz[i]; N += siz[i]; } for (int i = 1; i < n; i++) { int u, v, w1, w2; cin >> u >> v >> w1 >> w2; add(u, v, w1, w2); add(v, u, w1, w2); } dfs1(1, 0); dfs2(1, 0, __int128(0)); __int128 ans = sum - dif; string str; while (ans) { str = (char)((ans % 10) + 48) + str; ans /= 10; } cout << str << endl; return 0; }
- 1
信息
- ID
- 8824
- 时间
- 1000ms
- 内存
- 128MiB
- 难度
- 6
- 标签
- 递交数
- 0
- 已通过
- 0
- 上传者