poj2054 Color a Tree(贪心)

题意

要给一棵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;
}

猜你喜欢

转载自blog.csdn.net/a_bright_ch/article/details/81099096