poj2054 Color a Tree

时间:2021-11-25 10:09:30

神题。这题是巨毒瘤...

自己写真可谓是:

排空驭气奔如电,上天入地求之遍

上穷碧落下黄泉,两处茫茫皆不见

由于我们知道:不是树形时,不停选值最大的节点可以得到最小代价。

那么我们就能想出一个错误的贪心:每次从能选的中选出最大的。

下面我们来构*例:


root

↙     ↘

1         2

100


贪心:2 + 2 + 300 = 304

实际:1 + 200 + 6 = 207

那么我们该如何处理呢?

完全搞不倒啊!看题解看题解

得出如下结论:最大的节点一定是紧随着父节点染色的。

下一步:没有下一步....?????

我们要把这两个节点合并!

等效权值为平均数!

很遗恨的是,我只会证明两个点时权值是平均数。更多的还有待考证...

不过做题就是要靠想象!这就是正确的!

所以我们在代码实现的难题上继续败北......

我的想法是每个节点记录所有子节点以及所有合并过来的节点的顺序。用两个vector来实现。

然后那个堆是最困扰我的地方,实现起来困难重重。

看一看标程:cnm!直接n²暴力!

1000 * 1000 ......我觉得还行

然后我还发现了一件事:它没有记录次序!合并的同时更新ans!

惊为天人!

代码要简洁高效。这一点在寒假时我就有体会,现在又有这种感觉。

 /**
poj 2054
*/
#include <cstdio>
using namespace std;
typedef long long LL;
const int N = ;
const double eps = 1e-; struct Node {
int tot, n, fa;
bool del;
double val;
}node[N]; int n, R; inline int find() {
double maxval = -;
int ans = ;
for(int i = ; i <= n; i++) {
if(!node[i].del && i != R && node[i].val - maxval > eps) {
maxval = node[i].val;
ans = i;
}
}
return ans;
} inline int getfa(int k) {
while(node[k].del) {
k = node[k].fa;
}
return k;
} int main() {
scanf("%d%d", &n, &R);
while(n || R) {
LL ans = ;
node[R].fa = ;
for(int i = ; i <= n; i++) {
scanf("%d", &node[i].tot);
node[i].val = (double)node[i].tot;
node[i].n = ;
node[i].del = ;
ans += node[i].tot;
}
for(int i = ; i < n; i++) {
int x, y;
scanf("%d%d", &x, &y);
node[y].fa = x;
}
//printf("\n");
for(int i = ; i < n; i++) {
int s = find();
int f = getfa(node[s].fa);
//printf("%d %d\n", s, f);
ans += node[f].n * node[s].tot;
node[f].n += node[s].n;
node[f].tot += node[s].tot;
node[f].val = (double)(node[f].tot) / node[f].n;
//printf("%f\n", node[f].val);
node[s].del = ;
}
printf("%I64d\n", ans);
scanf("%d%d", &n, &R);
}
return ;
}

AC代码