AcWing 115.给树染色

一颗树有 nn 个节点,这些节点被标号为:1,2,3…n1,2,3…n,每个节点 ii 都有一个权值 A[i]A[i]。

现在要把这棵树的节点全部染色,染色的规则是:

根节点 RR 可以随时被染色;对于其他点,在被染色之前它的父亲节点必须已经染上了色。

每次染色的代价为 T×A[i]T×A[i],其中 TT 代表当前是第几次染色。

求把这棵树染色的最小总代价。

输入格式

第一行包含两个整数 nn 和 RR,分别代表树的节点数以及根节点的序号。

第二行包含 nn 个整数,代表所有节点的权值,第 ii 个数即为第 ii 个节点的权值 A[i]A[i]。

接下来 n−1n−1 行,每行包含两个整数 aa 和 bb,代表两个节点的序号,两节点满足关系: aa 节点是 bb 节点的父节点。

除根节点外的其他 n−1n−1 个节点的父节点和它们本身会在这 n−1n−1 行中表示出来。

同一行内的数用空格隔开。

输出格式

输出一个整数,代表把这棵树染色的最小总代价。

数据范围

1≤n≤10001≤n≤1000,
1≤A[i]≤10001≤A[i]≤1000

输入样例:
5 1
1 2 1 2 4
1 2
1 3
2 4
3 5
输出样例:
33

题解 

首先,做这道题,我们可以从最简入手,来猜测最优解方案。我们可以先不考虑子节点和父节点以及根节点这些东西,单纯的把它看成一个排序,就是 a1 a2 a3 a4  .....an,我们怎样排序才能使题中运算方法的结果最小,,那么肯定是把最大的排前面,为什么?

把最大的排前面,它的系数是1,小的系数就大,如果排后面,那么它的系数更大。

接下来,我们也用这个思想,把大的排前面,但是这题中出现了子节点和父节点这类东西,我们可以考虑将平均值大的放前面,平均值小的放后面。为什么?

我们假设有两组数(已经排序好的),a1,a2.......an   ,b1,b2...........bm,  

Sab= 1a1+2a2+.......n*an+(n+1)b1+(n+2)b2+.............+(n+m)bm

Sba=  1b1+2b2+......m*bm+..(m+1)a1+(m+2)a2+.........(n+m)an

Sab-Sba=n(b1+b2+.......+bm)-m(a1+a2+.....an)

假设Sab<Sba(也就是a排在b的前面,毕竟要求结果最小吗),那么推出来的结果为(b1+b2+....bm)/m   <     (a1+a2+.....an)/n;

证毕

我们的做法就是根据这种思想,把非节点有关系的看成一组,顺便用个变量来存平均数,方便排序 ,但是又出现个新问题,怎么计算(是个难点)。

起初,每个单独的点就是一个组。

然后,我们把非节点中有关系的点合并成一个,我们利用这个过程来计算最终的结果,怎么个利用法?

假设有两组数 xi,yi,假设yi比xi大,那么结果为yi+2xi;我们利用这个过程来推

起初 直接相加 xi+yi , 在进行比较xi比yi小,所以xi往后排,那么它的系数就会增加yi的个数,所以结果直接加 xi乘以yi的个数。

最后我们把它们全合并到根节点。(因为根节点必须是第一个染色,题中说染色一个节点,那么它的父节点就必须染色,根节点是所有非节点中父节点的父节点)

 

#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;

int n,root;

const int N=1010;

struct Node
{
    int p;   //表示它的父节点
    int s;   //表示它的大小
    int v;   //表示它的值
    double ave;   //表示平均值
}nodes[N];

int find()
{
    double ave=0;
    int res=-1;
    //对每个节点进行遍历
    for(int i=1;i<=n;i++)
    {
        if(i!=root&&nodes[i].ave>ave)   //找到非父节点并且它的平均值较大
        {
            ave=nodes[i].ave;
            res=i;
        }
    }
    
    return res;
}

int main()
{
    cin>>n>>root;
    int ans=0;
    for(int i=1;i<=n;i++)
    {
         cin>>nodes[i].v;
         nodes[i].s=1;
         nodes[i].ave=nodes[i].v;
         ans+=nodes[i].v;
    }
    
    //确定子父关系
    for(int i=0;i<n-1;i++)
    {
        int a,b;
        cin>>a>>b;
        nodes[b].p=a;
    }
    //首先根节点必须第一个点亮,因为题中说要染色一个节点,就必须先染色它的父节点
    //然而,根节点是所有父节点的父节点
    for(int i=0;i<n-1;i++)  //循环n-1次,找到每个节点
    {
        int p=find();  //找到一个节点
        int father=nodes[p].p;
        ans+=nodes[p].v*nodes[father].s;   //将它并入父节点
        nodes[p].ave=-1;    //将它删除(并不是直接删除,而是让它等于一个不可能取到的数)
        for(int j=1;j<=n;j++)
        {
            if(nodes[j].p==p)  nodes[j].p=father;        //将它的子节点全部指向它的父节点
        }
        //更新父节点
        nodes[father].v+=nodes[p].v;
        nodes[father].s+=nodes[p].s;
        nodes[father].ave=(double)nodes[father].v/nodes[father].s;
    }
    
    cout<<ans<<endl;
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值