题目
[CodeForces 1394D] Boboniu and Jianghu
分析
考虑树形 DP:对于当前点
u
u
u 和它的子节点
v
v
v,若
h
u
≠
h
v
h_u \neq h_v
hu=hv,那么这条边的方向就确定;若
h
u
=
h
v
h_u = h_v
hu=hv,我们需要给这条边定向。假设最终定完
u
u
u 所有邻接边的向后
u
u
u 的入度为
x
x
x,出度为
y
y
y,那么
u
u
u 对答案的贡献就是
t
u
×
max
{
x
,
y
}
t_u \times \max\{x, y\}
tu×max{x,y}。
于是可以定义状态:
d
p
[
u
]
[
0
/
1
]
dp[u][0/1]
dp[u][0/1] 为
u
u
u 的父亲边朝向
u
u
u
/
/
/ 背向
u
u
u 时
u
u
u 子树对答案的最小贡献。
树上给边定向的套路就是先给边钦定一个方向(例如我们钦定这些边都指向 v v v)并统计代价(代价为 d p [ v ] [ 0 ] dp[v][0] dp[v][0]),然后把改变该边方向的代价(即 d p [ v ] [ 1 ] − d p [ v ] [ 0 ] dp[v][1] - dp[v][0] dp[v][1]−dp[v][0])放入数组中排序。从改变代价小的开始改变边的方向即可。
上述方法为什么是对的呢?因为对于一种改变了 p p p 条边方向的方案,其对“ u u u 对答案的贡献”的改变量是一定的(不考虑父亲边就是 t u × max { x + p , y − p } t_u \times \max\{x + p, y - p\} tu×max{x+p,y−p}),那么只需要贪心地取边改变量最小的 p p p 条即可。
代码
#include <bits/stdc++.h>
int Read() {
int x = 0; char c = getchar();
while (c < '0' || c > '9')
c = getchar();
while (c >= '0' && c <= '9')
x = x * 10 + (c ^ 48), c = getchar();
return x;
}
typedef long long LL;
const int MAXN = 200000;
int N;
int A[MAXN + 5], B[MAXN + 5];
std::vector<int> G[MAXN + 5];
LL Ans;
LL Dp[MAXN + 5][2];
void Dfs(int u, int f) {
LL tot = 0;
int out = 0, in = 0;
std::vector<LL> num;
for (int v: G[u]) {
if (v != f) {
Dfs(v, u);
if (A[u] < A[v])
tot += Dp[v][1], in++;
else {
tot += Dp[v][0], out++;
if (A[u] == A[v])
num.push_aback(Dp[v][1] - Dp[v][0]);
}
}
}
std::sort(num.begin(), num.end());
Dp[u][0] = tot + (LL)B[u] * std::max(in + (u != 1), out);
Dp[u][1] = tot + (LL)B[u] * std::max(in, out + (u != 1));
for (int val: num) {
in++, out--, tot += val;
Dp[u][0] = std::min(Dp[u][0], tot + (LL)B[u] * std::max(in + (u != 1), out));
Dp[u][1] = std::min(Dp[u][1], tot + (LL)B[u] * std::max(in, out + (u != 1)));
}
}
int main() {
N = Read();
for (int i = 1; i <= N; i++)
B[i] = Read();
for (int i = 1; i <= N; i++)
A[i] = Read();
for (int i = 1; i < N; i++) {
int u = Read(), v = Read();
G[u].push_back(v);
G[v].push_back(u);
}
Dfs(1, 0);
printf("%lld", Dp[1][0]);
return 0;
}