$Poj2054\ Color\ a\ Tree\ $ 贪心
$Description$
一颗树有 $n$ 个节点,这些节点被标号为:$1,2,3…n,$每个节点 $i$ 都有一个权值 $A[i]$。
现在要把这棵树的节点全部染色,染色的规则是:
根节点R可以随时被染色;对于其他节点,在被染色之前它的父亲节点必须已经染上了色.
每次染色的代价为$T*A[i]$,其中$T$代表当前是第几次染色.
求把这棵树染色的最小总代价。
$Sol$
贪心:树中权值最大的点,一定会在它的父结点染色后立即染色
可以这样想,假设没有树的约束,只是一个序列,那么显然是按照从大到小排序的顺序染色为最优,现在有树的约束条件,也应该让权值最大的尽量早地染色.
因为权值最大的点与它的父结点的染色是接连进行的,所以我们可以把它们合并起来.
现在有权值为$x,y,z$的三个点,已知$x$和$y$的染色是接连着进行的,其中$x$是$y$的父结点.那么有两种决策.
1.先染$x,y$,再染$z$ ,$x+2y+3z=y+(x+y)+3z$
2.先染$z$,再染$x,y, z+2x+3y=z+y+2*(x+y)$
发现无论是上面的哪种情况有一种做法是通用的:先把$y$累加进答案,然后把$y$结点与$x$结点合并,具体来说,$x$的权值改为$(x+y)/2$,删除$y$结点.为什么是把权值改成$(x+y)/2$呢?
有一个十分重要的问题,就是如何判断这两种做法的优劣.设合并后的结点为$i$,这个结点所包含的结点数(它自己和合并进去的)为$s[i]$,包含的所有的权值和为$a[i]$.
1.$a[i]+(s[i]+1)*z.$
2.$z+a[i]*2.$
发现应该把$1$式中的$z$的系数变成$2$,把两个式子同时加上$(s[i]-1)*z$,再同除$s[i]$,变成
1.$a[i]/s[i]+2*z$
2.$z+a[i]/s[i]$
可以看成权值为$a[i]/s[i]$的结点和$z$的两个结点,根据最开始所说的贪心可知比较它们的大小就能决定先给谁染色了,于是问题就解决了.
还是要强调的是上面所赋予的新权值只能用于与别的结点比较大小从而决定染色顺序,至于答案的累加,就在对于最初始的两个式子的分析中"把$y$累加进答案"这句话.具体来说$j$结点累加进$i$结点,$ans+=a[j]*s[i]$.
$Code$
#include<iostream> #include<cstdio> #define il inline #define Rg register #define go(i,a,b) for(Rg int i=a;i<=b;i++) #define yes(i,a,b) for(Rg int i=a;i>=b;i++) #define db double #define ll long long using namespace std; il int read() { int x=0,y=1;char c=getchar(); while(c<'0'||c>'9'){if(c=='-')y=-1;c=getchar();} while(c>='0'&&c<='9'){x=(x<<1)+(x<<3)+c-'0';c=getchar();} return x*y; } int n,rt,a[1001],fa[1001],s[1001]; ll as; int main() { //while(1) { n=read(),rt=read();as=0; if(!n && !rt)return 0; go(i,1,n)a[i]=read(),s[i]=1; go(i,1,n-1){int x=read(),y=read();fa[y]=x;} go(T,1,n-1) { db maxs=0;int pos; go(i,1,n) if(i!=rt && 1.0*a[i]/s[i]>=maxs)maxs=1.0*a[i]/s[i],pos=i; go(i,1,n) if(fa[i]==pos)fa[i]=fa[pos]; as+=a[pos]*s[fa[pos]]; s[fa[pos]]+=s[pos]; a[fa[pos]]+=a[pos]; a[pos]=0; } as+=a[rt]; printf("%lld\n",as); } return 0; }