1 题目描述
一颗树有
现在要把这棵树的节点全部染色,染色的规则是:
根节点
每次染色的代价为
求把这棵树染色的最小总代价。
输入格式
第一行包含两个整数
第二行包含
接下来
除根节点外的其他
同一行内的数用空格隔开。
输出格式
输出一个整数,代表把这棵树染色的最小总代价。
数据范围
输入样例:
5 1
1 2 1 2 4
1 2
1 3
2 4
3 5
输出样例:
33
2 解析
如果没有 “先选父节点” 的限制,每轮可以任意选择一个点染色,那么本题就是一个 排序不等式 的结论题。
考虑加了这一限制后,如何处理本问题,易发现一个简单性质:
对含有最大权值的结点,对其父节点染色后,下一个染色对象必然是他(排序不等式易证)。
既然该结点与他的父节点的染色顺序是相邻的,根据该性质,我们可以将这两个点 合并 成一个点,合并后的新结点权值,为两个点权值的平均值。
例如有权值
- 先
后 ,其代价为: - 先
后 ,其代价为:
做差易得:
因此,
将上述简单情况推广到一般情况,假设有两组点
- 先
后 ,其代价为: - 先
后 ,其代价为:
做差易得:
由此得到一个 “等效权值” 的算法:记录每个点是由多少个点合并而成的,一个点的 “等效权值” 定义为:
最终做法是:不断在树中找到 “等效权值” 最大的点
关于答案统计
我这里给出一个完全不同的思路,不用像 y 总那样推导出每次便宜后的总值,思维量会稍微小一点。
不难发现 “等效权值” 点所代表的一类点可以看做一个集合,那么不妨用 带权并查集 来维护每个等效权值点:
- 点权:在根节点维护这个集合的 “等效权值” 以及集合的大小。
- 边权:用边权维护在这个集合中该节点的次序。
这样最后整个树中只会有一个并查集,因此每个点到根的路径长,就是他在染色过程中的次序。
int find(int u)
{
if (p[u] != u)
{
int t = find(p[u]);
d[u] += d[p[u]];
p[u] = t;
}
return p[u];
}
void merge(int a, int b)
{
p[a] = b;
d[a] += siz[b];
siz[b] += siz[a];
}
int get_maxid()
{
double maxv = 0;
int id = -1;
for (int i = 1; i <= n; i ++ )
{
if (i != root && find(i) == i && v[i] > maxv)
{
id = i;
maxv = v[i];
}
}
return id;
}
int main()
{
cin >> n >> root;
for (int i = 1; i <= n; i ++ )
cin >> w[i], v[i] = w[i], d[i] = 0, p[i] = i, siz[i] = 1;
for (int i = 1, a, b; i < n; i ++ )
cin >> a >> b, fa[b] = a;
for (int i = 1; i < n; i ++ )
{
int t = get_maxid(), father = find(fa[t]);
v[father] = (v[t] * siz[t] + v[father] * siz[father]) / (siz[t] + siz[father]);
merge(t, father);
}
int res = 0;
for (int i = 1; i <= n; i ++ )
res += (d[i] + 1) * w[i];
cout << res << endl;
return 0;
}
3 相关题解
| 题目 | OJ | 难度 |
|---|---|---|
| Color a tree | spoj | 提高+/省选- |