poj2054 Color a Tree

题意

给你一棵树
每一个点有一个权值 ai a i
要给树染色,必须先染父亲再染儿子
如果这个点是第 i i 个染色的,那么他的代价是iai
要求代价最小

题解

一开始想了一个鬼畜贪心,我不知道他为什么是错的。。但是我也不知道他为什么能对,十分尴尬。反正他WA了,那就不管哪个做法了。。
考虑,如果当前节点 i i ,他的权值是最大的,那么我们肯定是选完他的父亲立刻选他
于是他和他的父亲顺序就决定了,他的父亲下一个就是他
于是我们可以把这两个点看作一个
新点的权值就是他们的平均数,表示每延后一次回产生多少代价
n1次,你就会剩下一个点,然后就做完了。。
剩下一个点时,所有点染色的顺序就出来了
这题 n2 n 2 可以过,但其实写一个堆时可以优化到 nlogn n l o g n

CODE:

#include<cstdio>
#include<algorithm>
#include<iostream>
#include<cstring>
using namespace std;
typedef long long LL;
const LL N=1005;
LL a[N];
LL f[N],g[N],h[N],nxt[N],lst[N];
LL fa[N];
struct qq
{
    LL x,y,last;
}e[N*2];LL num,last[N];
void init (LL x,LL y)
{
    num++;
    e[num].x=x;e[num].y=y;
    e[num].last=last[x];
    last[x]=num;
}
LL findfa (LL x)    {return f[x]==x?f[x]:f[x]=findfa(f[x]);}
void dfs (LL x)
{
    for (LL u=last[x];u!=-1;u=e[u].last)
    {
        LL y=e[u].y;
        if (fa[x]==y) continue;
        fa[y]=x;dfs(y);
    }
}
int main()
{
    LL n,rt;
    while (true)
    {
        num=0;memset(last,-1,sizeof(last));
        scanf("%lld%lld",&n,&rt);
        if (n+rt==0) break;
        for (LL u=1;u<=n;u++)   scanf("%lld",&a[u]);
        for (LL u=1;u<n;u++)
        {
            LL x,y;
            scanf("%lld%lld",&x,&y);
            init(x,y);init(y,x);
        }
        fa[rt]=0;dfs(rt);
        for (LL u=1;u<=n;u++)   {f[u]=u;g[u]=a[u];h[u]=1;lst[u]=u;nxt[u]=0;}
        LL x,y,id;//g[u]/h[u]>x/y  -->   g[u]*y>h[u]*x
        for (LL u=1;u<n;u++)
        {
            x=0;y=1;
            for (LL i=1;i<=n;i++)
            {
                LL xx=findfa(i);
                if (xx==rt) continue;
                if (g[xx]*y>h[xx]*x)    {id=xx;x=g[xx];y=h[xx];}
            }
            //id和他的父亲合并
            LL xx=findfa(fa[id]);
            //printf("YES:%lld %lld\n",id,xx);
            LL last=lst[xx];
            lst[xx]=lst[id];nxt[last]=id;f[id]=f[xx];
            h[xx]=h[xx]+h[id];
            g[xx]=g[xx]+g[id];
        }
        LL now=rt,ans=0;
        for (LL u=1;u<=n;u++)
        {
            //printf("now:%lld\n",now);
            ans=ans+a[now]*u;
            now=nxt[now];
        }
        printf("%lld\n",ans);
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值