题意: 给你一颗树,根节点是1,每个节点有一个代价a[i],我们每个点有一个原始数字b[i],还有一个目标数字c[i](b[i],c[i]∈[0,1]),你可以挑选一个节点,然后给这个节点的k个子节点(0<=k<=子树大小)的原始数字任意排序,这个操作的代价是k*a[i],现在要使所有节点的数字都转变成目标数字,最小代价是多少?
思路:由于需要让答案尽可能小,所以能想到很明显的一个贪心策略,每次肯定会选择花费最小的结点去改变他的子树结构。
所以有个想法是先DFS预处理其子树的情况之后,之后根据节点的权值大小进行排序,之后进行根据权值来枚举节点来改变其子树。但是发现如果子子树情况改变比较难更新上面结点的状态,于是这个想法破产。
当我们DFS的时候,如果我们发现当前结点的权值是此时最小的,那么我们就可以直接用当前权值对他的子树进行操作。其实就相当于一个DP,先向下推,处理出从上往下的最小值,每次都是从之前已经处理过的状态向上推。向上推的时候处理出处理过后的0和1的数量。之后仅在为当前最小权值节点进行更改即可。如果最后到上推到1节点发现,还有1和0剩余那么就是没有答案。
#include <bits/stdc++.h> using namespace std; const int maxn = 2e5+10; typedef long long ll; ll a[maxn], ans; int b[maxn], c[maxn], cnt[maxn][2], id[maxn]; vector<int> G[maxn]; void DFS(int u, int fa, int minn) { minn = min(a[u], 1ll*minn); for (int i = 0; i < G[u].size(); ++ i) { int v = G[u][i]; if (v == fa) continue; DFS(v, u, minn); cnt[u][0] += cnt[v][0]; cnt[u][1] += cnt[v][1]; } if (b[u] != c[u]) { if (b[u] == 1) cnt[u][1] ++; else cnt[u][0] ++; } if (minn == a[u]) { int minc = min(cnt[u][0], cnt[u][1]); ans += a[u] * minc * 2; cnt[u][0] -= minc; cnt[u][1] -= minc; } } int main() { int n; scanf("%d", &n); for (int i = 1; i <= n; ++ i) { scanf("%lld%d%d", &a[i], &b[i], &c[i]); } for (int i = 1; i <= n-1; ++ i) { int u, v; scanf("%d%d", &u, &v); G[u].push_back(v); G[v].push_back(u); } DFS(1, 0, 1e9); if (cnt[1][0] | cnt[1][1]) ans = -1; printf("%lld\n", ans); return 0; }