一颗树有 nn 个节点,这些节点被标号为:1,2,3…n1,2,3…n,每个节点 ii 都有一个权值 A[i]A[i]。
现在要把这棵树的节点全部染色,染色的规则是:
根节点 RR 可以随时被染色;对于其他节点,在被染色之前它的父亲节点必须已经染上了色。
每次染色的代价为 T×A[i]T×A[i],其中 TT 代表当前是第几次染色。
求把这棵树染色的最小总代价。
输入格式
第一行包含两个整数 nn 和 RR,分别代表树的节点数以及根节点的序号。
第二行包含 nn 个整数,代表所有节点的权值,第 ii 个数即为第 ii 个节点的权值 A[i]A[i]。
接下来 n−1n−1 行,每行包含两个整数 aa 和 bb,代表两个节点的序号,两节点满足关系: aa 节点是 bb 节点的父节点。
除根节点外的其他 n−1n−1 个节点的父节点和它们本身会在这 n−1n−1 行中表示出来。
同一行内的数用空格隔开。
输出格式
输出一个整数,代表把这棵树染色的最小总代价。
数据范围
1≤n≤10001≤n≤1000,
1≤A[i]≤10001≤A[i]≤1000
输入样例:
5 1
1 2 1 2 4
1 2
1 3
2 4
3 5
输出样例:
33
解题思路以及代码:
思路:
/*
解题误区:利用贪心易想到先染色权值最大的,然后依次染色权值最小的
这种思路是错误的是因为当这个权值最大的后面的子节点权值都较小,而另一个次大
的子节点的权值都偏大,很明显可以看出此时的贪心并不是最优解
解题思路:
1:虽然上面解法是错误的,但可以得出一个正确得结论:每次找出一个权值最大的点,
当他的父节点被染色时,他应该立即被染色;
2:假设有3个点,分别为a(权值最大值的根节点), b(权值最大的值), c(其他点),对这
3个点进行染色共有2种情况:
{
情况1:先染色a, b再染色c:则染色的代价为a + 2 * b + 3 * c;
情况2:先染色c,在染色a, b:染色的代价为c + 2 * a + 3 * b;
两式相减——>2 * c - (a + b)
所以当2 * c - (a + b) < 0 时,即c < (a + b) / 2时,选择情况1
又因为a染色后b必须立即染色,所以可以把a, b压缩为一个点,权值是(a + b) / 2;
看成一个点若这个点的平均值越小则先被染色。
}
3:将点染色推广到每组染色
{
比如有两组:
Sa = a1 + a2 + ....+ an, Sb = b1 + b2 + .....bm;
则先染色Sa再染色Sb代价为:Sab = Sa + (n + 1) * Sb;
先染色Sb再染色Sa代价为:Sba = Sb + (m + 1) * Sa
两式相减:S = n * Sb - m * Sa;
当S小于0是-->Sb / m < Sa / n
即当那个组的平均值越小谁越先被染色
将Sa组和并到Sb组的代价为:Sa.size * Sb.sum;
此公式是因为再Sb合并之前Sa已经合并完了,所以
Sb的时间要加上Sa种的节点的数量,然后乘自身的总和;
}
4:总体思路:
每次找出当前权值最大(即平均值最大)的非根节点
将其染色顺序排在紧随父节点之后的位置
然后将该点合并进父节点中,更新父节点的权值
直到将所有点都合并进根节点为止
*/
代码:
#include <cstdio>
#include <iostream>
using namespace std;
const int N = 1010;
struct Node
{
int fa, size, sum;//fa为父节点,size为当前点里面的节点数量,sum为该节点的和
double avg;//avg为节点平均数,即为权重
}node[N];
int n, root;
int find()//找到权值最大的前(即平均值最大的点)
{
double avg = 0;
int res = -1;
for (int i = 1; i <= n; i ++ )
if (i != root && node[i].avg > avg)
{
res = i;
avg = node[i].avg;
}
return res;
}
int main()
{
cin >> n >> root;
int res = 0;
for (int i = 1; i <= n; i ++ )
{
int x;
scanf("%d", &x);
node[i].size = 1;//开始还未开始合并节点数只有自己
node[i].sum = x;
node[i].avg = x;
res += x;//所有染色的代价先把每个点的和累加方便后续计算
}
for (int i = 0; i < n; i ++ )
{
int a, b;
scanf("%d %d", &a, &b);
node[b].fa = a;
}
for (int i = 0; i < n - 1; i ++ )
{
int ver = find();//找到权值最大的前(即平均值最大的点)
int p = node[ver].fa;//找到该节点的父节点
res += node[p].size * node[ver].sum;//将该节点合并到父节点的代价:
node[ver].avg = -1;//将这个点去掉,设置成-1防止find()被找到;
for (int j = 1; j <= n; j ++ )//如何将a合并到b上呢?
if (node[j].fa == ver)//只需要将a的子节点直接指向b即可
node[j].fa = p;
node[p].sum += node[ver].sum;//合并后的sum等于合并前的sum加新增的sum
node[p].size += node[ver].size;
node[p].avg = (double)node[p].sum / node[p].size;
}
cout << res << endl;
return 0;
}