贪心的总体思路是:每次找到一个权值最大的节点,如果它是根节点,则首先对它染色,
否则的话我们可以得出一个结论,在对它的父亲已经染色的情况下,立刻给它染色是最优的。
现在重点讨论第二种情况,当它不是根节点时,我们如果对它父亲染了色,则一定会立刻对它染色,
所以可以把它和它父亲合并为同一个节点,它和它父亲的儿子都成为了新节点的儿子,它的父亲的父亲则是新节点的父亲。
为了能继续贪下去,我们给每个节点赋上两个权值,
num_node表示对第i个节点图色所需的时间(第i个节点实际包含的节点数),sumc表示第i个节点的总权值(第i个节点实际包含的节点的权值和)。贪心目标是sumc/num_node的原因应该是因为如果想要为一个节点涂色,必须将一条路线都涂色。
题目样例分析:
粉刷顺序与合并顺序是不一样的,
粉刷顺序为: 1,3,5,2,4
合并顺序为:
fa num_node(包含点数) sumc(当前权值) ans(当前花费)
5 3 1 4 4 他父亲(3)加4+4
3 1 2 5 9 他父亲(1)加14=9*1+5
2 1 1 2 2 他父亲(1)加8=3*2+2
4 1 1 2 2 他父亲(1)加10=4*2+2
最后33 = 1 + 14 + 8 + 10
#include<iostream>
#include<stdio.h>
#include<algorithm>
#include<queue>
#include<string.h>
using namespace std;
struct node{
int num_node; //此集合中包含点数
int father; //父亲
int sumc; //总权值
int ans; //它和它的子节点所用花费
int flag; //表示它已访问过
}Node[1001];
int find(int p) {//Tree[p].father如果不是根节点且Tree[p].father已粉刷就能继续往上找
if (Node[p].father != p&&Node[Node[p].father].flag)
Node[p].father = find(Node[p].father);
return Node[p].father;
}
int main() {
int N, R;
while (~scanf("%d %d", &N, &R)) {
memset(Node, 0, sizeof(Node));
if (N == 0 && R == 0)
break;
for (int i = 1; i <= N; i++) {
scanf("%d", &Node[i].sumc);
Node[i].ans = Node[i].sumc;
Node[i].num_node = 1;
}
Node[R].father = R;
for (int i = 1; i < N; i++) {
int f, s;
scanf("%d %d", &f, &s);
Node[s].father = f;
}
int index;
double maxv;
for (int i = 1; i < N; i++) {
maxv = 0;
for (int j = 1; j <= N; j++) {
if (!Node[j].flag&&maxv < Node[j].sumc*1.0 / Node[j].num_node&&j != R) {//找到未被访问的平均花费最大的节点
index = j;
maxv = Node[j].sumc*1.0 / Node[j].num_node;
}
}
Node[index].flag=1;
int father = find(index);
Node[father].ans += Node[index].ans + Node[index].sumc*Node[father].num_node;
//重要:父亲的总花费=孩子总花费+孩子总权值*父亲的顺序值
Node[father].sumc += Node[index].sumc;
Node[father].num_node += Node[index].num_node;
}
printf("%d\n", Node[R].ans);
}
}