如果2、3、4各节点的权值为8 10 5,那么3第一个被选中,同时2的节点值为18,个数为2,平均权值为9,与节点4进行比较,节点4权值为5。这里之所以用18/2与4比较 是因为对234与423进行比较,23肯定是连在一起的,因为是3必须最先被使用,由于父节点必须先使用,因此使用完2之后必须立马使用3.回到234与423的比较,两个的差值即为(8+10)*1-4*2.
如果2,3,4权值各位10,6,8,那么2优先被使用,至于3则不用关心,因为3只是2的子节点,第二个选择的是节点4,最后选择节点3,在下面的代码里,会自动把3节点排在4的后面(+),
题意:给定一棵树,每个节点有一个权值,现要求给这些节点进行排列,设排列后的节点顺序为v1~vn,它们的权值是w1~wn,那么我们要求一种排列使得w1*1+w2*2+...+wn*n最小。还有一个限制就是这个排列满足每个节点的父节点都排在该结点之前。
分析:试想,如果没有父节点排在节点之前的限制,那么这个题目非常简单,只需要将结点按照权值从大到小排列即可。加上了这个限制之后,应该把权值最大的点满足父节点之后的条件后,尽可能排在最前面。
代码中
for(i=parent[x];next[i]!=-1;i=next[i]); //排序,接到最后一个节点上
next[i]=x;
prev[x]=i;
对于一对父子节点也不一定完全直接相连,可能他们中间插有其他节点,但是子节点一定在父节点之后,即通过访问子节点的父节点,再依次访问父节点的NEXT节点,可得到最后接入的位置。当然,如果父节点NEXT域为空,说明该子节点是最先与父节点相连的,直接放在父节点NEXT里
当然最先选择的最大节点,并不一定最终最先使用, 因为他们要与父节点结合在一起,因此要将父节点、子节点结合在一起后的平均权值与其他节点序列的平均权值进行比较,才能确定优先顺序。
for(i=parent[x];prev[i]!=-1;i=prev[i]); //合并节点
rank[i]+=rank[x];
sum[i]+=sum[x];
这里是将确定已经直接相连的节点序列看做一个整体,计算其平均权值,这里不能一直用i=parent[i]进行迭代,因为可能父子之间插有其他节点。
这里用parent开始统计同一个序列的平均权值,不用担心parent本身不是该序列的顶点。(假设访问顺序为1,2,3,5,4)访问节点4时,先算parent得到3,(实际上连续序列与4相连的应该是5,)继续用pre递推得到1,从而修改1的平均权值,虽然走的路径不对,但最终必须要走到parent节点的,因为父节点在子节点前面嘛,不用担心4与3之间还有一个节点x没有被访问从而导致跳过某个节点,因为否则3-5-4-x与x-4顺序矛盾。
这里依然把4节点加入到节点1中,虽然路径不对,但是最终还是加入到了顶端1节点。
这里之所以没有用pre一直递推,而是在第一步用parent后面才用pre递推,是因为有可能第一步用pre得到空值。
我们假设绑定在一起的两节点是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 <stdio.h>
#include <stdlib.h>
#define MAXN 1010
int n,r;
int c[MAXN];
int parent[MAXN];
bool visited[MAXN];
int next[MAXN];
int prev[MAXN];
int rank[MAXN];
int sum[MAXN];
int find()
{
double max=0;
int i,flg=-1;
for(i=1;i<=n;i++)
if(max<sum[i]*1.0/rank[i] && !visited[i])
{
max=sum[i]*1.0/rank[i];
flg=i;
}
return flg;
}
void uni(int x)
{//合并节点,并把他们按顺序排好
int i;
for(i=parent[x];prev[i]!=-1;i=prev[i]); //合并节点
rank[i]+=rank[x];
sum[i]+=sum[x];
for(i=parent[x];next[i]!=-1;i=next[i]); //排序,接到最后一个节点上
next[i]=x;
prev[x]=i;
visited[x]=true;
}
int main()
{
while(scanf("%d %d",&n,&r),n && r)
{
int i;
for(i=1;i<=n;i++)
{
scanf("%d",&c[i]);
visited[i]=false;
prev[i]=next[i]=-1;
rank[i]=1; //合并后,集合中的规模
sum[i]=c[i]; //集合的和
}
for(i=1;i<n;i++)
{ //输入n-1个节点的父子关系
int a,b;
scanf("%d %d",&a,&b);
parent[b]=a;
}
visited[r]=true;
while(true)
{
int u=find();
if(u==-1)
break;
uni(u);
}
int cnt=0,ans=0; //cnt用于记录时间
for(i=r;i!=-1;i=next[i])
ans+=(++cnt)*c[i];
printf("%d\n",ans);
}
return 0;
}