题意:给定一棵树,每个节点有一个权值,现要求给这些节点进行排列,设排列后的节点顺序为v1~vn,它们的权值是w1~wn,那么我们要求一种排列使得w1*1+w2*2+...+wn*n最小。还有一个限制就是这个排列满足每个节点的父节点都排在该结点之前。
分析:试想,如果没有父节点排在节点之前的限制,那么这个题目非常简单,只需要将结点按照权值从大到小排列即可。加上了这个限制之后,如果权值最大的那个节点一旦满足了条件(父节点被排在了之前的某个位置),那么这个权值最大的节点一定要紧挨着这个父节点,即把这个权值最大的节点排在它所能排的最前面的位置。因为对于这个节点如果不受限制应该排在第一位,而有了限制,在满足了限制之后也应把它尽可能地排在前面。所以它一定是挨着父节点的。那么现在在最终的排列中我们确定了两个节点的前后相邻关系,将他们绑定在了一起。
试想如果保持这个相邻关系的同时去掉其他节点的限制,那么我们应该如何排列呢?我们假设绑定在一起的两节点是a和b。现有一个另外的节点x,我们看两种排列xab,abx对最终的计算结果有什么影响。x*i+a*(i+1)+b*(i+2); a*i + b*(i+1) + x*(i+2)。后者减去前者等于2x-(a+b)。即将x从ab之前挪到ab之后,ab各左移1位,结果减小a+b。x右移2位结果增加2x。因此两者谁在前谁在后我们只需要比较a+b和2x即可,也可以比较(a+b)/2和x。
将这个定理进行一下推广,绑定在一起的不一定是两个节点,可以是一个更长的序列,与这个序列进行比较看谁放在前面的也可以是一个序列。设一个序列有n1个节点,第二个序列有n2个节点。那么我们比较两者谁放在前面的时候需要比较的是(n1个权值之和×n2)和(n2个权值之和×n1)。即左移和右移产生的结果变化。当然也可以比较(n1个权值之和/n1)和(n2个权值之和/n2)。
我们可以再次进行推广,如果我们要排列的不是节点,而是许多序列的话,那么我们只需要计算每个序列权值的平均数(例如:n个节点的序列,要计算n个权值之和/n),然后按照这个平均数从大到小排列即可使得计算结果最小。这样就可以让序列与节点有了一个统一的衡量值——平均数。
这样一来,我们就可以将上面的绑定两节点的操作看成是将问题规模缩小的操作,在帮定两节点的同时我们在树中也将两节点合并,变为一个节点,即将子节点的孩子变为父节点的孩子。然后合并后的节点的权值是合并在这个节点中的所有节点的权值的平均数。我们成功的将问题规模减小了1。只需要不断这样做即可将问题缩减为只有一个节点。
实现过程中,应在树中每个节点记录并入该节点的个数和权值和。树的存储与绑定前后关系的记录要分开存储,用一个数组单独记录前后绑定的排序关系。
#include <cstdio> #include <cstring> using namespace std; #define MAX_NODE_NUM 1005 struct Node { int value; int father; int next; int child; int id; int num; }node[MAX_NODE_NUM]; int root_id, node_num; bool vis[MAX_NODE_NUM]; int link[MAX_NODE_NUM]; int original_value[MAX_NODE_NUM]; void add_edge(int father, int son) { int temp = node[father].child; node[father].child = son; node[son].next = temp; node[son].father = father; } void input() { memset(node, -1, sizeof(node)); for (int i = 0; i < node_num; i++) { scanf("%d", &node[i].value); node[i].id = i; node[i].num = 1; original_value[i] = node[i].value; } for (int i = 0; i < node_num - 1; i++) { int node_a, node_b; scanf("%d%d", &node_a, &node_b); node_a--; node_b--; add_edge(node_a, node_b); } } void merge_with_father(int id) { int father = node[id].father; node[father].value += node[id].value; node[father].num += node[id].num; node[father].id = node[id].id; int end = id; while (node[end].next != -1) end = node[end].next; node[end].next = node[id].child; while (node[end].next != -1) { end = node[end].next; node[end].father = father; } //printf("father:%d son:%d ", father + 1, id + 1); } bool larger(int a, int b) { if (a == -1) return true; return node[a].value * node[b].num < node[b].value * node[a].num; } void work() { memset(vis, 0, sizeof(vis)); vis[root_id] = true; while (1) { int p = -1; for (int i = 0; i < node_num; i++) if (!vis[i] && larger(p, i)) p = i; if (p == -1) break; vis[p] = true; link[node[node[p].father].id] = p; // printf("a:%d b:%d ", node[node[p].father].id + 1, p + 1); merge_with_father(p); } int temp = root_id; int ans = 0; for (int i = 1; i <= node_num; i++) { // printf("%d ", temp + 1); ans += i * original_value[temp]; temp = link[temp]; } printf("%d ", ans); } int main() { while (scanf("%d%d", &node_num, &root_id), node_num | root_id) { root_id--; input(); work(); } return 0; }