这题首先手玩一下一下数据,写出每个节点修建软件所需要的时间和到达它的时间戳(第一次到达它的时间),不难发现实际上就是要最小化这两者之和。然后就想到:一棵子树内,时间戳必然是连续的一段区间,而如果将访问到子树根节点的时间看做0时,则是一段0~x的连续时间,与其他子树的分配无关。所以自然的联想到单独处理出dp[u]:以u为根节点,访问时间看做0的时候其中节点所需要的时间最大值。
如何从dp[v]转移到dp[u]?首先随便给它们分配一个顺序,则每一个节点的最后最大值(原先的值建立在访问根节点的时间为0的基础上,但现在的根节点变成了原来根节点的父亲)=原来的dp值+分配的顺序所带来的时间差。我们分析应怎样分配这个顺序使得它们的最大值最小。
我们有
dp[u] = max{ dp[a] , dp[b] + size[a] + 2 };
dp[u] = max{ dp[u] , max{ dp[b], dp[a] + size[b] + 2 }}; // size[x] 代表访问x子树的全部节点所需要的时间
这个应该不难看出:实际上就是枚举了两种情况。那么如果第一次取得的值要小于第二次取得的值,我们就认为将第一个放在第二个前面是更优的。之所以+2,是因为由u->v的边还需要经过2次。
// luogu-judger-enable-o2 #include <bits/stdc++.h> using namespace std; #define maxn 600000 #define int long long int n, a[maxn], size[maxn]; int cnp = 1, dp[maxn], head[maxn]; int read() { int x = 0; char c; c = getchar(); while(c < '0' || c > '9') c = getchar(); while(c >= '0' && c <= '9') x = x * 10 + c - '0', c = getchar(); return x; } struct node { int id, num, s; }P[maxn]; struct edge { int to, last; }E[maxn * 2]; bool cmp (node a, node b) { int p = max(a.num, b.num + a.s + 2); int q = max(b.num, a.num + b.s + 2); return p < q; } void add(int u, int v) { E[cnp].to = v, E[cnp].last = head[u]; head[u] = cnp ++; } void DP(int u, int fa) { if(u != 1) dp[u] = a[u]; for(int i = head[u]; i; i = E[i].last) { int v = E[i].to; if(v == fa) continue; DP(v, u); } int tot = 0, last = 0; for(int i = head[u]; i; i = E[i].last) if(E[i].to != fa) P[++ tot] = (node) {E[i].to, dp[E[i].to], size[E[i].to]}; if(tot) { sort(P + 1, P + 1 + tot, cmp); for(int i = 1; i <= tot; i ++) { int v = P[i].id; dp[u] = max(dp[u], dp[v] + (size[u] + 1)); size[u] += size[v] + 2; } } } signed main() { n = read(); for(int i = 1; i <= n; i ++) a[i] = read(); for(int i = 1; i < n; i ++) { int x = read(), y = read(); add(x, y), add(y, x); } DP(1, 0); printf("%lld ", max(dp[1], (n - 1) * 2 + a[1])); return 0; }