E. Tree Shuffling(树上问题 + 贪心 )

题意: 给你一颗树,根节点是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
", ans);


    return 0;
}
原文地址:https://www.cnblogs.com/Vikyanite/p/14678222.html