题意
要给一棵N个节点的树染色,染节点i时,它的父亲节点必须已经染色,根除外。每个节点有一个权值a[i],第T次染色的代价是T*a[i]。求最小的代价使所有点被染色。
题解
贪心
想要简单的选择最大权值并优先给其染色的想法是错误的。但是如果i节点的儿子s是权值最大的节点,在给i染完色后立刻给s染色是必然的。
根据这个性质,我们可以把i和s两个点捆绑在一起,缩成一个点,并用其平均数来替代它的权值,意思是我们已经知道了这部分的染色顺序。(为什么平均数?)
把i和s的定义扩大,看成是两个节点集。不仅仅单点与单点之间能被捆绑,还可以把两个点集进行捆绑,这样这棵树就能逐渐被缩成一个点了。
看到这如果能实现请一试,成功的请告知我一声。
下面就来讲讲实现。
理论很美妙,但实现很复杂。膜了大神们的博客,我们有上面的思想再往下看。
题意其实可以看成是有N个人在买面包,他们排了一个树形的队,每分钟售货员只能处理一份订单,如果i在第T分钟订单才被处理,他会产生T*a[i]的不满意值,求最小的不满意值。
把问题分成一些小问题来看。因为上述的思想是一个逆推的思想,我们还要尝试着从后往前来推出答案。
先找到权值最大的点i,它的父亲fa[x],将其缩点。这时ans+=a[x]*1,意思是x要等fa[x] 1分钟,会增加a[x]*1的不满意值。
重复如此。
如果遇到两个点集怎么理解呢?设为点集x和ffa,设立fact记录这群人等1分钟产生的不满意值(点集中权值和),cnt记录这群人让后面的人又等了几分钟(点集中点的数量)。当x要与ffa合并时,ans+=fact[x]*cnt[ffx],意思是x中的人又要多等cnt[ffa]分钟,会增加fact[x]*cnt[ffx]的不满意值。
理解清楚,看看我的实现吧,可能写得比较复杂,有些地方直接暴力枚举写得更方便些。
vector本来是想写priority_queue的,但是priority_queue的更新不够迅速,导致top不准确,才改成用vector。
fa搭配fav是用来求某个节集的上一个点集用的,用了并查集中的路径压缩的思想,fav[x]==true等效于模版中的fa[x]==x这样一个判“首领”,也就是集合标志。
代码
#include<queue>
#include<vector>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long ll;
const int maxn=1010;
int n,rt;
int fa[maxn];bool fav[maxn];//fa[i]记录在i上面的集合的编号,fav记录它是否是集合的顶部
int fact[maxn],cnt[maxn];//fact[i]记录当前集合i中所有数的和,cnt[i]记录集合i中的点数
double ave[maxn];//ave[i]记录集合i中的平均值
vector<int> q;
int find_ffa(int x)//找到x的上一个集合
{
if(fav[x]==true) return x;
return fa[x]=find_ffa(fa[x]);
}
int find(int &p)//寻找平均值最大的点,p是其位置
{
double mx=0;int re;
for(int i=0;i<q.size();i++)
{
if(mx<ave[q[i]])
{
mx=ave[q[i]];
re=q[i];
p=i;
}
}
return re;
}
int main()
{
while(scanf("%d%d",&n,&rt),n!=0)
{
memset(fav,true,sizeof(fav));
ll ans=0;
for(int i=1;i<=n;i++)
{
int x;
scanf("%d",&x);
fact[i]=x;cnt[i]=1;
ave[i]=x;
ans+=x;
if(i!=rt) q.push_back(i);//根没法加入集合,它已经处于树的顶端
}
for(int i=1;i<n;i++)
{
int x,y;
scanf("%d%d",&x,&y);
fa[y]=x;//开始时每个点都是一个集合
}
for(int i=1;i<n;i++)
{
int p,x=find(p);q.erase(q.begin()+p);
//把x与它的集合与ffa合并
int ffa=find_ffa(fa[x]);
ans+=fact[x]*cnt[ffa];
fact[ffa]+=fact[x];cnt[ffa]+=cnt[x];//要把x集合的信息转移到ffa上
ave[ffa]=(double)fact[ffa]/cnt[ffa];
fav[x]=false;fa[x]=ffa;//x不再是集合顶,ffa成为x所在集合的顶部
}
printf("%lld\n",ans);
}
return 0;
}