[POJ2054]Color A Tree
题目大意:多组数据,给出有N<=1000个结点的树和点权,为结点 i 染色需要一单位时间,花费为染色时刻 T[i] * 点权W[i] ,只有为父亲染过色才能为孩子染色,求为所有结点染色最小花费
看了评论才想起来是调整法证明的贪心:
如果现有染色序列 A xxxx…k个结点…xxx B
将其调整为 B A xxxx…k个结点…xxx
则染色时刻变化为 T[A] -> T[A]+1
T[K……] -> T[K……]+1
T[B] -> T[B]-(k+1)
则染色代价的变动 Δcost = + ∑点权W[A和K……] * 变动时刻1 - 点权W[B] * 变动时刻(k+1)
那么当且仅当 Δcost<0 时,后一种方案比前者更优
把 Δcost 变换一下 Δcost<0 推出 ∑W[AK..]-W[B]*(k+1) < 0
∑W[AK..] < W[B]*(k+1)
∑W[AK..] / (k+1) < W[B] / 1
即是说当 W[B] >∑W[AK..] / (k+1) 时,将B放在A前染色会更优
为每个结点设P[B]=∑W[B] / kB,当P[B]为当前最大时,将B与其父F合并,合并后的结点F’
P[F'] = (∑W[B]+∑W[F]) / (kB + kF)
W[F']=∑W[B]+∑W[F]
kF' = kB + kF
花费为∑W[B]
P.S.可以用堆维护取最大值的过程,优化时间
#include<cstdio>
#include<cstring>
#include<iostream>
#define N 1500
using namespace std;
int n,m,root,w[N],f[N],s[N],res,id[N],son[N];
float p[N];
void swap(int i,int j){id[i]^=id[j];id[j]^=id[i];id[i]^=id[j];}
void heapify(int st,int end){
int t;
while((st<<1) < end){
if(p[id[st<<1]] > p[id[(st<<1)+1]]) t=(st<<1);
else t=(st<<1)+1;
if(p[id[t]] < p[id[st]]) break;
swap(st,t);
st=t;
}
if((st<<1) == end && p[id[st]] < p[id[end]]) swap(st,end);
}
int father(int i){
if(son[i]==i) return i;
else return son[i]=father(son[i]);
}
int main(){
int i,j,k,t,u,v;
while(scanf("%d%d",&n,&root),n&&root){
memset(son,0,sizeof(son));
res=0;
for(i=1;i<=n;i++){
scanf("%d",&w[i]);
s[i]=1;res+=w[i];
son[i]=id[i]=i;
p[i]=float(w[i]);
}
for(i=1;i<n;i++){
scanf("%d%d",&j,&k);
f[k]=j;
}
id[root]=n;
m=n-1;
for(i=m/2;i>0;i--) heapify(i,m);
i=m;
while(i--){
while(son[id[1]]!=id[1]){
id[1]=id[m--];
heapify(1,m);
}
t=id[1];
k=father(f[t]);
res+=w[t]*s[k];
if(k!=root){
son[k]=t;
s[t]+=s[k];
w[t]+=w[k];
f[t]=f[k];
p[t]=((float)(w[t])/(float)(s[t]));
heapify(1,m);
}
else{
s[k]+=s[t];
son[t]=root;
}
}
printf("%d\n",res);
}
return 0;
}