传送门
题意
给出一颗树,根为 1 号节点,点有点权 \(a\),求从根出发一条遍历每条边恰好两次的路径,使得以下式子最小:
记 \(t_i\) 为第一次到达一个点时走过的路径条数,特别地 \(t_1 = 2 \times n - 2\)(最后回到 1)
思路
显然是树形 dp,记 \(f_u\) 为 \(u\) 子树内的最小值答案,\(g_u\) 走整个子树一遍后回到 \(u\) 的距离。
每遍历一个儿子都执行上述转移。
每次执行时,第一个式子里的 \(g_u\) 表示遍历 \(v\) 以前的所有儿子走一遍再回到 \(u\) 的代价,\(+1\) 则是到 \(v\) 的代价。
于是关键在于遍历儿子的顺序,考虑贪心。考虑通过调整法来推导出最优解的性质。
如,两个儿子 \(v_1, v_2\)。调整前遍历 \(v_1\) 后遍历 \(v_2\):
那么从 \(v_1,v_2\) 都没遍历向都遍历的转移为:
若交换 \(v_1, v_2\):
那么 \(v_1\) 在 \(v_2\) 前面优于 \(v_2\) 在 \(v_1\) 前面(不交换优于交换)的条件为:
注意到:\(g_{v_1} + 2 + f_{v_2} \gt f_{v_2}\),所以,若满足条件,右侧必须是第二个值成为 \(\max\),即:
又 \(f_{v_1} \lt g_{v_2} + 2 + f_{v_1}\) 恒成立,则有用的只有:
移项得:
于是每个 \(u\),将儿子按照 \(g_{v} - f_v\) 从小到大排序,然后转移即可。
代码:
link
const int N = 1e6 + 5;
int n;
int a[N];
vector<int> e[N];
int f[N], g[N];
void dfs(int u, int fa){if(u != 1) f[u] = a[u];vector<int> vec;for(int v : e[u]){if(v == fa) continue;dfs(v, u);vec.push_back(v);}sort(vec.begin(), vec.end(), [](int v1, int v2){return g[v1] - f[v1] < g[v2] - f[v2];});for(int v : vec){chkmx(f[u], g[u] + f[v] + 1);g[u] += g[v] + 2;}
}
void solve_test_case(){n = read();rep(i, 1, n){a[i] = read();}rep(i, 1, n - 1){int u = read(), v = read();e[u].push_back(v);e[v].push_back(u);}dfs(1, 0);write(max(f[1], g[1] + a[1]));
}