Address
https://www.lydsy.com/JudgeOnline/problem.php?id=3677
http://uoj.ac/problem/105
Solution
考虑一个子树内的两种情况:
容易得出一个树形 dp 状态:
f[u]
f
[
u
]
表示以
u
u
为根的子树,连接 和其父节点的边为红色,
u
u
的子树内的最大收益。如果不存在方案则为 (图 1 )。
g[u]
g
[
u
]
表示以
u
u
为根的子树,连接 和其父节点的边为蓝色,
u
u
的子树内的最大收益。如果不存在方案则为 (图 2 )。
特殊地,如果
u
u
是整棵树的根(我们不失一般性地选 为根),则
f[u]
f
[
u
]
表示整棵树的最大收益。
如果
u
u
是叶子,那么
f f 的转移即考虑所有子树:
其中 len(u,v) l e n ( u , v ) 为边 (u,v) ( u , v ) 的长度。
g g 的转移则是枚举 的子节点 v v 使 为蓝边:
需要使用前缀 & 后缀最大值优化。
看上去最后答案就是 f[1] f [ 1 ] ,但是很遗憾,不是。为什么呢?
我们忽略了一种情况:
但继续分析可以发现,下图这种情况是不会发生的:
这样就能得出:存在一个点 r r ,最优解在以 为根的子树上不会出现我们之前忽略的这种情况。
所以,以每个点为根进行 dp 就能达到目的。但这是 O(n2) O ( n 2 ) 的。考虑到枚举根特别浪费时间,可以在树形 dp 上使用换根技巧, O(n) O ( n ) 求出以所有点为根的答案。
这样,答案就是以所有点为根的 f f <script type="math/tex" id="MathJax-Element-4136">f</script> 的最大值。
Code
#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#define For(i, a, b) for (i = a; i <= b; i++)
#define Rof(i, a, b) for (i = a; i >= b; i--)
#define Tree(u) for (int e = adj[u], v; e; e = nxt[e]) if ((v = go[e]) != fu)
using namespace std;
inline int read() {
int res = 0; bool bo = 0; char c;
while (((c = getchar()) < '0' || c > '9') && c != '-');
if (c == '-') bo = 1; else res = c - 48;
while ((c = getchar()) >= '0' && c <= '9')
res = (res << 3) + (res << 1) + (c - 48);
return bo ? ~res + 1 : res;
}
const int N = 2e5 + 5, M = N << 1, INF = 0x3f3f3f3f;
int n, ecnt, nxt[M], adj[N], go[M], val[M], f[N], g[N], h[N], s[N], ps[N],
suml[N], sumr[N], maxl[N], maxr[N], len[N], va[N], _l[N];
void add_edge(int u, int v, int w) {
nxt[++ecnt] = adj[u]; adj[u] = ecnt; go[ecnt] = v; val[ecnt] = w;
nxt[++ecnt] = adj[v]; adj[v] = ecnt; go[ecnt] = u; val[ecnt] = w;
}
void dfs(int u, int fu) {
int i, cnt = 0; Tree(u) cnt++, dfs(v, u);
if (!cnt) return (void) (f[u] = 0, g[u] = -INF);
int sp = 0; f[u] = g[u] = 0; Tree(u) {
int orz = max(f[v], val[e] + g[v]);
if (orz < 0) sp++; else f[u] += orz;
}
if (sp) f[u] = -INF; cnt = 0; Tree(u) suml[++cnt] = sumr[cnt]
= max(f[v], val[e] + g[v]), ps[cnt] = v, len[cnt] = val[e];
For (i, 1, cnt) suml[i] += suml[i - 1]; sumr[cnt + 1] = 0;
Rof (i, cnt, 1) sumr[i] += sumr[i + 1]; g[u] = -INF; if (!sp) For (i, 1, cnt) {
int v; if (f[v = ps[i]] >= 0)
g[u] = max(g[u], len[i] + f[v] + suml[i - 1] + sumr[i + 1]);
}
}
void dfs2(int u, int fu) {
int i, sp = 0, rp = 0, cnt = 0; if (fu) {
va[++cnt] = max(h[u], _l[u] + s[u]); len[cnt] = _l[u];
ps[cnt] = -1; if (va[cnt] < 0) rp++;
}
Tree(u) {
va[++cnt] = max(f[v], val[e] + g[v]); len[cnt] = val[e];
ps[cnt] = v; if (va[cnt] < 0) sp++;
}
suml[0] = sumr[cnt + 1] = 0; maxl[0] = maxr[cnt + 1] = -INF;
For (i, 1, cnt) suml[i] = suml[i - 1] + va[i],
maxl[i] = max(maxl[i - 1], ps[i] == -1 ? h[u] + len[i] - va[i] :
f[ps[i]] + len[i] - va[i]);
Rof (i, cnt, 1) sumr[i] = sumr[i + 1] + va[i],
maxr[i] = max(maxr[i + 1], ps[i] == -1 ? h[u] + len[i] - va[i] :
f[ps[i]] + len[i] - va[i]);
Tree(u) h[v] = s[v] = -INF; if (rp || sp > 1) {
Tree(u) _l[v] = val[e], dfs2(v, u); return;
}
if (sp) {
int v = 1; while (va[v] >= 0) v++;
h[ps[v]] = sumr[1] - va[v]; s[ps[v]] = sumr[1] - va[v]
+ max(maxl[v - 1], maxr[v + 1]);
}
else {
For (i, (fu ? 2 : 1), cnt) {
int v = ps[i]; h[v] = sumr[1] - va[i];
s[v] = sumr[1] - va[i] + max(maxl[i - 1], maxr[i + 1]);
}
}
Tree(u) _l[v] = val[e], dfs2(v, u);
}
int main() {
int i, x, y, z; n = read(); For (i, 1, n - 1)
x = read(), y = read(), z = read(), add_edge(x, y, z);
int ans = 0; dfs(1, 0); dfs2(1, 0); For (i, 1, n)
ans = max(ans, max(f[i] + h[i], f[i] + s[i] + _l[i]));
cout << ans << endl; return 0;
}