1. 程式人生 > 其它 >[題解][UVA-1205]Color a Tree

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

\(\Longrightarrow\)原題連結

首先是一個錯誤的貪心:每次選擇一個當前可以染色的權值最大的節點進行染色。反例也容易找,只需要在一個權值很小的節點下面放一堆權值很大的節點,然後在另一顆子樹中放一個比那個權值很小的點大一點點的節點就好了。

但是這個貪心可以給我們一些啟示,當我們染了一個有兒子的點之後,這個點的權值最大的兒子一定會在接下來立刻染色。

既然這兩個點是相繼被染色的,那麼可以將其合併成一個點,但是合併之後的節點的權值到底如何計算,這是個問題。

現在假設有三個節點,他們的權值分別是 \(a, b, c\),已知前兩個節點相繼被染色,那麼對於這三個節點染色的順序,有兩種決策,他們對答案的影響分別是:

  1. \(a + 2b + 3c\)
  2. \(c + 2a + 3b\)

運用簡單的相減法對兩個式子進行大小比較:

\[a + 2b + 3c - c - 2a - 3b = 2c - (a + b) = 2(c - \frac{a + b}{2}) \]

可以發現,實際上就是第三個節點的權值和前兩個節點的權值平均值進行比較。

接著,我們可以搞出一種“等效權值”,用於記錄合併之後的點團的價效比:

\[權值總和/點團大小 \]

接著,我們每次找到一個“等效權值”最大的點團,將其和其父親的點團進行合併,合併之前因為計算出這個點團因為在父親之後被染色而對答案的影響。

#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;
}