题意:给一棵树染色,这棵树有n个节点,根结点是r。每个结点都有一个权值ci,开始时间为0,每染色一个结点需要耗时1,每个结点染色代价为ci*ti(ti为当前时间),每个结点只有在父结点被染色的条件下才能被染色。求染完整棵树要花费的最小代价。
分析:贪心总体思路,每次找一个权值最大的节点,如果是根节点,则首先对它染色,否则的话可以得出一个结论,在它父亲已经染色的情况下,立刻给它染色是最优的。对于第二种情况,当它不是根节点,且已对它父亲染了色,则一定会立刻对它染色,所以可以把它和它父亲合并为同一个节点,它和它父亲的儿子都成为了新节点的儿子,它的父亲的父亲则是新节点的父亲。为了能继续贪心,给每个节点赋上两个权值,ti表示对第i个节点图色所需时间(第i个节点实际包含的节点数),si表示第i个节点的总权值(第i个节点实际包含的节点的权值和)。此时,定义一个节点的权值等于si比上ti。可以证明在新的权值定义下,以上贪心同样成立。
结论:对于一个非根结点,它具有非根结点的最大权值,那么访问完它的父节点后立即访问它能使得代价最小。
使用并查集。
过程;
1、初始将序列中的time[i]都置为1,w[i]置为c[i];
2、查找最大的w[i],得到位置;
3、将该位置的s与它的父节点s合并(就是C_i / T_i,C_i = c[该节点] + c[父节点],T_i = time[该节点]+time[父节点])得新的父节点w[](w[父节点] = C_i / T_i),如果有节点与pos相连,让它指向pos的父节点。
4、重复2、3,直到合并完(或者所有点都被访问);
最终会得到一个访问序列,该序列能得到最小的染色权值。
#include<iostream>
using namespace std;
#define N 1010
double b[N]; //合并节点权值和
int Next[N]; //记录最优访问的下一个节点
int f[N]; //记录父节点
int cnt[N]; //合并节点的节点数
int fa[N]; //并查集使用
bool vis[N];
int a[N]; //权值
int child(int r) //找到最优访问序列的最后一个节点(子节点)
{
if(Next[r]==r) return r;
return child(Next[r]);
}
int father(int x) //找到祖先节点,带路径压缩
{
if(fa[x]==x) return x;
fa[x]=father(fa[x]);
return fa[x];
}
int sovle(int n,int r)
{
double max;
int j,i,u,v,ans,k;
memset(vis,false,sizeof(vis));
vis[r]=true;
for(j=1;j<n;j++) //处理n-1次
{
max=0;
for(i=1;i<=n;i++)
if(!vis[i] && max<b[i]/cnt[i])
{
max=b[i]/cnt[i];
v=i;
}
u=child(f[v]);
Next[u]=v;
fa[v]=u;
u=father(v); //这句并不多余,可以进行路径压缩
cnt[u]+=cnt[v];
b[u]+=b[v];
vis[v]=true;
}
ans=0;
k=1;
while(r!=Next[r]) //处理最优序列
{
ans+=k*a[r];
k++;
r=Next[r];
}
ans+=k*a[r];
return ans;
}
int main()
{
int n,i,u,v,r;
while(scanf("%d%d",&n,&r)==2 && n+r)
{
for(i=1;i<=n;i++)
{
scanf("%d",&a[i]);
b[i]=a[i]*1.0;
fa[i]=f[i]=Next[i]=i;
cnt[i]=1;
}
for(i=1;i<n;i++)
{
scanf("%d%d",&u,&v);
f[v]=u;
}
printf("%d\n",sovle(n,r));
}
return 0;
}
HDU ACM 1055 Color a Tree->树上的贪心
原文地址:http://blog.csdn.net/a809146548/article/details/46391581