题目链接
题意
给你一个树,告诉你根节点。树上每个节点有一个权值
w
i
w_i
wi,你需要将树的每个节点进行染色。
每次染色花费
1
1
1 的时间,一个点可以染色需要满足,根节点或者相邻节点已经被染色。
染色的花费为
时
间
∗
w
i
时间*w_i
时间∗wi,求全部染色的最小花费。
思路
首先权值最大点和其父节点染色操作是连续的。
假设
x
,
y
,
z
x,y,z
x,y,z 三点,
x
x
x 和
y
y
y 染色连续
先染
x
,
y
x,y
x,y,再染
z
z
z 花费为
x
+
2
∗
y
+
3
∗
z
x+2*y+3*z
x+2∗y+3∗z
先染
z
z
z,再染
x
,
y
x,y
x,y 花费为
z
+
2
∗
x
+
3
∗
y
z+2*x+3*y
z+2∗x+3∗y
x
+
2
∗
y
+
3
∗
z
?
2
∗
x
+
3
y
+
z
x+2*y+3*z ? 2*x+3y+z
x+2∗y+3∗z?2∗x+3y+z
=
(
x
+
y
)
/
2
+
2
∗
z
?
z
+
2
∗
(
(
x
+
y
)
/
2
)
=(x+y)/2+2*z ? z+2*((x+y)/2)
=(x+y)/2+2∗z?z+2∗((x+y)/2)
=
z
?
(
x
+
y
)
/
2
=z?(x+y)/2
=z?(x+y)/2
判断如何染色,可将最大值归入其父节点,得到一个新的等效节点(等效节点 = 该点包含权值总和÷该点包含节点数)。
继续缩点,直到全部的点都被判断完。
缩点转移可使用并查集,向根方向合并
代码
#include <stdio.h>
#include <string.h>
using namespace std;
int w[1005], f[1005], num[1005], ans[1005], pre[1005];
int getf(int a)
{
return a == pre[a] ? a : getf(pre[a]);
}
int main()
{
#ifndef ONLINE_JUDGE
freopen("1.in","r",stdin);
freopen("1.out","w",stdout);
#endif
int n, r;
while(scanf("%d%d",&n,&r), n)
{
f[r] = 0;
for(int i = 1; i <= n; ++i) scanf("%d",&w[i]), num[i] = 1, pre[i] = i;
for(int i = 1; i < n; ++i)
{
int v, u;
scanf("%d%d",&v,&u);
f[u] = v;
}
pre[0] = 0;
num[0] = 1;
memset(ans,0,sizeof(ans));
for(int i = 1; i <= n; ++i)
{
int a = 0;
for(int j = 1; j <= n; ++j)
{
if(pre[j] == j && (!a || w[a]*num[j] < num[a]*w[j])) a = j;
}
int fa = getf(f[a]);
ans[fa] += ans[a]+w[a]*num[fa];
w[fa] += w[a];
num[fa] += num[a];
pre[a] = fa;
}
printf("%d\n",ans[0]);
}
return 0;
}
/*
6 1
2 1 6 5 5 4
1 2
2 3
3 4
3 5
1 6
0 0
91
*/