[Luogu] UVA1205 Color a Tree
Description
有一棵树,需要给其所有节点染色,每个点染色所需的时间是一样的,都是\(1\)。给每个点染色,还有一个开销为“当前时间\(×c_i\)”,\(c_i\)是每个节点的一个权值。(当前时间是染完这个节点的时间)
染色还有另一个约束条件,要染一个点必须要先染好其父节点,所以第一个染的点是根节点。
求最小开销。
Solution
神奇的贪心题。
如果没有先后染色的顺序,肯定会先把最大的节点先染了,但是如果有了限制,类似的,就会把子节点中最大的点先染了,所以两个操作会是连续的。
所以假设待染色的点分别为\(x,y,z\),其中\(x,y\)已知是连续染色,则只有两种染法
\(1.\)\(x+2y+3z\),先染\(x,y\),再染\(z\)
\(2.\)\(z+2x+3y\),先染\(z\),再染\(x,y\)
比较两数大小,也就是比较\(\frac{(x+y)}{2}\)与\(z\)。
这也就启发我们运用平均值作为性价比,每次扫描一遍求出最大性价比进行染色,直到最后将整棵树缩成一个点,得到最终答案。
然后缩点可以用并查集。
Code
#include <bits/stdc++.h>
using namespace std;
int n, rt, res, a[1005], c[1005], t[1005], f[1005], fa[1005], vis[1005];
double val[1005];
int read()
{
int x = 0, fl = 1; char ch = getchar();
while (ch < '0' || ch > '9') { if (ch == '-') fl = -1; ch = getchar();}
while (ch >= '0' && ch <= '9') {x = (x << 1) + (x << 3) + ch - '0'; ch = getchar();}
return x * fl;
}
int gf(int x)
{
return x == fa[x] ? x : fa[x] = gf(fa[x]);
}
void merge(int x, int y)
{
fa[x] = y;
return;
}
void work()
{
int pos; double mx = 0;
for (int i = 1; i <= n; i ++ )
if (i != rt && val[i] > mx && !vis[i])
mx = val[i], pos = i;
int ff = gf(f[pos]);
vis[pos] = 1;
res += c[pos] * t[ff] + a[pos];
t[ff] += t[pos]; c[ff] += c[pos]; val[ff] = (double)(c[ff]) / (double)(t[ff]);
//这一步非常重要。t和val的处理都很好理解,但c为什么也要累加呢?我的理解是这样处理c[x],其实就是x和x的子树中被合并的点权之和。这样上面更新res的时候才是对的。(因为一个点的时间增加,它的子树内所有的点的时间也都会增加)
merge(pos, ff);
return;
}
int main()
{
while (1)
{
n = read(), rt = read();
f[rt] = 0;
memset(vis, 0, sizeof(vis));
if ((!n) && (!rt)) break;
for (int i = 1; i <= n; i ++ ) a[i] = read(), c[i] = a[i];
res = c[rt];
for (int i = 1; i <= n - 1; i ++ )
{
int x = read(), y = read();
f[y] = x;
}
for (int i = 1; i <= n; i ++ ) fa[i] = i, t[i] = 1, val[i] = c[i];
for (int i = 1; i <= n - 1; i ++ ) work();
printf("%d\n", res);
}
return 0;
}