[题解][UVA-1205]Color a Tree

⟹ \Longrightarrow 原题链接

首先是一个错误的贪心:每次选择一个当前可以染色的权值最大的节点进行染色。反例也容易找,只需要在一个权值很小的节点下面放一堆权值很大的节点,然后在另一颗子树中放一个比那个权值很小的点大一点点的节点就好了。

但是这个贪心可以给我们一些启示,当我们染了一个有儿子的点之后,这个点的权值最大的儿子一定会在接下来立刻染色。

既然这两个点是相继被染色的,那么可以将其合并成一个点,但是合并之后的节点的权值到底如何计算,这是个问题。

现在假设有三个节点,他们的权值分别是 a , b , c a, b, c a,b,c,已知前两个节点相继被染色,那么对于这三个节点染色的顺序,有两种决策,他们对答案的影响分别是:

  1. a + 2 b + 3 c a + 2b + 3c a+2b+3c
  2. c + 2 a + 3 b c + 2a + 3b c+2a+3b

运用简单的相减法对两个式子进行大小比较:
a + 2 b + 3 c − c − 2 a − 3 b = 2 c − ( a + b ) = 2 ( c − a + b 2 ) a + 2b + 3c - c - 2a - 3b = 2c - (a + b) = 2(c - \frac{a + b}{2}) a+2b+3cc2a3b=2c(a+b)=2(c2a+b)
可以发现,实际上就是第三个节点的权值和前两个节点的权值平均值进行比较。

接着,我们可以搞出一种“等效权值”,用于记录合并之后的点团的性价比:

权 值 总 和 / 点 团 大 小 权值总和/点团大小 /
接着,我们每次找到一个“等效权值”最大的点团,将其和其父亲的点团进行合并,合并之前因为计算出这个点团因为在父亲之后被染色而对答案的影响。

#include <iostream>
#include <cstring>
#include <cstdio>

const int maxn = 1e5 + 5;
int n, r, fa[maxn], cnt[maxn], ans, sw[maxn];
double w[maxn];

int getMaxSw() {
    int ret; double maxv = 0;
    for (int i = 1; i <= n; i++) 
        maxv < w[i] && r != i ? maxv = w[ret = i] : maxv = maxv;
    return ret;
}

int main() {
    while (scanf("%d%d", &n, &r), n || r) {
        ans = 0;
        for (int i = 1; i <= n; i++) {
            scanf("%d", &sw[i]);
            w[i] = sw[i], cnt[i] = 1, ans += sw[i];
        }
        for (int i = 1; i < n; i++) {
            int u, v;
            scanf("%d%d", &u, &v), fa[v] = u;
        }
        for (int i = 1; i < n; i++) {
            int p = getMaxSw(), fat;
            w[p] = 0, fat = fa[p];
            ans += sw[p] * cnt[fat];

            for (int j = 1; j <= n; j++)
                if (fa[j] == p) fa[j] = fat;
            sw[fat] += sw[p], cnt[fat] += cnt[p], w[fat] = (double)(sw[fat]) / cnt[fat];
        }
        printf("%d\n", ans);
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值