题意:给一棵树染色,这棵树有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; }
时间: 2024-12-15 03:27:55