[Luogu] UVA1205 Color a Tree

\(Link\)

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;
}
posted @ 2020-11-24 21:43  andysj  阅读(93)  评论(0编辑  收藏  举报