题目大意:
给出一棵有根树(有向边),每个节点权值为val[i],要求把所有点组成一个序列并且父亲节点排在子节点前面,使得ans=val[1]*1+val[2]*2+……+val[n]*n最小…
分析:
看到此题第一想法就是val大的放前面…但是这是在没有限制的情况下…如果我们现在限制父节点一定比子节点靠前那么对于val大的子节点来说当然是紧挨着父节点最好…所以我们可以得到一些二元组(也就是把子节点和父节点绑定)…
现在我们有xy这个二元组和z这个单独节点,考虑怎样排列使得结果最优…
假设先选xy优于先选z,那么我们可以得到式子i*x+(i+1)*y+(i+2)*z < i*z+(i+1)*x+(i+2)*y…化简得到x+y>2*x,也就是(x+y)/2>x…这不就是说x+y的平均值大的优先选么…
现在我们把二元组和点扩展为两个序列…可以得到同样的结论…平均值大的优先选…所以我们只要每次选出val最大的点,然后把当前节点与其父节点合并(val变为平均值)就可以确定序列的顺序了…
代码如下:
#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdio>
//by NeighThorn
using namespace std;
const int maxn=1000+5;
int n,r,fa[maxn],sum[maxn],tim[maxn],cnt,ans;
double val[maxn];
signed main(void){
while(scanf("%d%d",&n,&r)&&!(!n&&!r)){
cnt=ans=0;
for(int i=1;i<=n;i++)
scanf("%lf",&val[i]),ans+=(int)val[i],sum[i]=val[i],tim[i]=1;
for(int i=1,x,y;i<n;i++)
scanf("%d%d",&x,&y),fa[y]=x;//有向图
while(cnt<n-1){
cnt++;int v;double MAX=0;//每次找序列中最大的进行合并
for(int i=1;i<=n;i++)
if(val[i]>MAX&&i!=r)
MAX=val[i],v=i;
for(int i=1;i<=n;i++)
if(fa[i]==v)
fa[i]=fa[v];//合并之后要更新当前点的子节点的fa
ans+=sum[v]*tim[fa[v]];val[v]=0;//tim[fa[v]]就是当前子序列中当前点前面有多少个点
tim[fa[v]]+=tim[v];sum[fa[v]]+=sum[v],val[fa[v]]=(double)sum[fa[v]]/(double)tim[fa[v]];
}
cout<<ans<<endl;
}
return 0;
}
by >_< NeighThorn