树染色问题color a tree(贪心)

本文转自 @ http://blog.csdn.net/gatieme

题目Color a Tree


Problem Description


Bob is very interested in the data structure of a tree. A tree is a directed graph in which a special node is singled out, called the “root” of the tree, and there is a unique path from the root to each of the other nodes.

Bob intends to color all the nodes of a tree with a pen. A tree has N nodes, these nodes are numbered 1, 2, …, N. Suppose coloring a node takes 1 unit of time, and after finishing coloring one node, he is allowed to color another. Additionally, he is allowed to color a node only when its father node has been colored. Obviously, Bob is only allowed to color the root in the first try.

Each node has a “coloring cost factor”, Ci. The coloring cost of each node depends both on Ci and the time at which Bob finishes the coloring of this node. At the beginning, the time is set to 0. If the finishing time of coloring node i is Fi, then the coloring cost of node i is Ci * Fi.

For example, a tree with five nodes is shown in Figure-1. The coloring cost factors of each node are 1, 2, 1, 2 and 4. Bob can color the tree in the order 1, 3, 5, 2, 4, with the minimum total coloring cost of 33.

Given a tree and the coloring cost factor of each node, please help Bob to find the minimum possible total coloring cost for coloring all the nodes.

Input


The input consists of several test cases. The first line of each case contains two integers N and R (1 <= N <= 1000, 1 <= R <= N), where N is the number of nodes in the tree and R is the node number of the root node. The second line contains N integers, the i-th of which is Ci (1 <= Ci <= 500), the coloring cost factor of node i. Each of the next N-1 lines contains two space-separated node numbers V1 and V2, which are the endpoints of an edge in the tree, denoting that V1 is the father node of V2. No edge will be listed twice, and all edges will be listed.

A test case of N = 0 and R = 0 indicates the end of input, and should not be processed.

Output


For each test case, output a line containing the minimum total coloring cost required for Bob to color all the nodes.

Sample Input


5 1
1 2 1 2 4
1 2
1 3
2 4
3 5
0 0

Sample Output


33

Source


Asia 2004, Beijing (Mainland China)

解题报告


题意


一棵树,结点树为n ,根结点为r 。 每个结点都有一个权值ci ,开始时间为0 ,每染色一个结点需要耗时1 ,每个结点的染色代价为ci*ti ( ti为当前的时间),每个结点只有在父结点已经被染色的条件下才能被染色。 求染完整棵树需要花费的最小代价。
题解:
结论证明来源: http://hi.baidu.com/cheezer94/item/7b4a15214b2050022b0f1c0a
结论1 :对于一个非根结点,它具有非根结点的最大权值,那么访问完它的父亲后就要立即访问它才能使得代价最小。
处理过程:
1 )建立结构体node ,结构体数组 node[i] 表示i结点的状态, node[i].c=ci 为总权值, node[i].w=ci 为当前权值, node[i].t=1 为经过这个结点需要的耗时(也可以理解为这个结点包含几个合并的结点), node[i].pre 为父结点
2 )找到一个最大权值非根结点,将其m与其父亲p合并形成一个新的结点,新结点还是原来p的位置,这个新结点的子结点为m和p的子结点;答案ans+= node[m].c*node[p].t ,表示经过父节点p后,需要经历 node[p].t 时间才到达m ,所以讲m同p合并后,总代价要加上这段路径的代价;新结点的情况 node[p].c+=node[m].c , node[p].t+=node[m].t , node[p].w=1.0*node[p].c/node[p].t (新结点权值变成算术平均值,因为到这个结点的代价被平分分给t个结点)。
3 )重复2 )直到结点只有一个为止。
4 ) ans+=∑ci ,因为本身染色需要耗时1 ,也就要支付代价ci*1.

AC代码1


#include<stdio.h>

#define M 1010

//#define DEBUG

/*
 *
 * 题意:
 * 一棵树,结点树为n ,根结点为r 。
 * 每个结点都有一个权值ci ,开始时间为0 ,每染色一个结点需要耗时1 ,
 * 每个结点的染色代价为ci*ti ( ti为当前的时间),
 * 每个结点只有在父结点已经被染色的条件下才能被染色。
 *
 * 求染完整棵树需要花费的最小代价。
 *
 * 题解:
 *
 * 结论证明来源: http://hi.baidu.com/cheezer94/item/7b4a15214b2050022b0f1c0a
 *
 * 结论1 :
 *  对于一个非根结点,它具有非根结点的最大权值,那么访问完它的父亲后就要立即访问它才能使得代价最小。
 *  因此我们每次查找权值最大的那个节点,然后向根开始归并,直接最终只剩下一个根位置
 *
 * 处理过程:
 *1 )建立结构体node ,结构体数组 node[i] 表示i结点的状态, node[i].c=ci 为总权值, node[i].w=ci 为当前权值, node[i].t=1 为经过这个结点需要的耗时(也可以理解为这个结点包含几个合并的结点), node[i].pre 为父结点
 * 2 )找到一个最大权值非根结点,将其m与其父亲p合并形成一个新的结点,新结点还是原来p的位置,这个新结点的子结点为m和p的子结点;答案ans+= node[m].c*node[p].t ,表示经过父节点p后,需要经历 node[p].t 时间才到达m ,所以讲m同p合并后,总代价要加上这段路径的代价;新结点的情况 node[p].c+=node[m].c , node[p].t+=node[m].t , node[p].w=1.0*node[p].c/node[p].t (新结点权值变成算术平均值,因为到这个结点的代价被平分分给t个结点)。
 * 3 )重复2 )直到结点只有一个为止。
 * 4 ) ans+=∑ci ,因为本身染色需要耗时1 ,也就要支付代价ci*1.
 *
 * */


typedef struct Node
{
    double w; //合并后的节点的平均权值
    int t; //合并之后的该节点所包含的步数
    int c; //合并之后的该节点的总权值
    int pre; //该节点的父节点};
}Node;
int n,r,a,b,sum;
Node node[M];


int find_max(int n, int r) //寻找节点之后的最大节点
{
    double max = -1;
    int p;

    for(int i = 1; i <= n; i++)
    {
        if(i == r)
            continue;
#ifdef DEBUG
        printf("当前节点%d的权值为%lf, 最大%f\n", i, node[i].w, max);
#endif
        if(node[i].w > max)
        {
            max = node[i].w;
            p = i;
        }
    }
#ifdef DEBUG
    printf("查找到当前权值最大的节点%d\n", p);
#endif

    return p;
}

int main()
{

//    freopen("1.txt", "rb", stdin);
    while(scanf("%d %d", &n, &r), n | r)
    {
        sum = 0;
        for(int i = 1;i <= n; i++)
        {
            scanf("%d", &node[i].c);

            node[i].w = node[i].c;
            node[i].t = 1;

            sum += node[i].c; //这边先保存一步走完全部节点的总权值,然后再去计算剩下的权值
        }

        for(int i = 1;i < n; i++)
        {
            scanf("%d %d", &a, &b);
            node[b].pre = a;
        }


        for(int i = 1;i < n; i++)
        {
            int m = find_max(n, r);

            node[m].w = 0; //把找到的最大权值节点的权值变为0,以免影响后面求最大节点的搜索

            int p = node[m].pre;

            sum += node[m].c * node[p].t;     // 表示经过父节后,需要经历node[p].t时间才到达m,所以讲m同p合并后,总代价要加上这段路径的代价;
#ifdef DEBUG
            printf("父节点%d, 染色节点%d, 代价%d * %d\n\n", p, m, node[m].c, node[p].t);
#endif

            //  将子节点m与其父节点p合并(子节点归属 | 步长合并 | 权值合并)
            //  合并后所有m的子节点成为p的子节点
            for(int j = 1; j <= n; j++)     // 将所有指向m节点的子节点的父节点该层p节点
            {
                if(node[j].pre == m)
                {
                    node[j].pre = p;
#ifdef DEBUG
                    printf("修改%d的父亲节点为%d\n", j, p);
#endif
                }
            }

            node[p].t += node[m].t;                 //  对总步数和总时间相加
            node[p].c += node[m].c;                 //  节点的权值

            // 合并后节点的加权权值应该变化为 总权值 / 步长, 应该拿这个权值与其他权值进行比较
            node[p].w = 1.0*node[p].c/node[p].t;    //  求平均的权值
#ifdef DEBUG
            printf("当前节点%d的步长为%d, 权值为%d, 平均权值为%lf\n\n", p, node[p].t, node[p].c, node[p].w);
#endif
        }
        printf("%d\n", sum);
    }

    return 0;

}

AC代码2


另外一种写法

#include <stdio.h>
#include <string.h>

#define maxn 1007
int total[maxn];
int c[maxn];    //  存储权值信息
int fa[maxn];   //  存储父子关系
int pre[maxn];  //  并查集
int num[maxn];  //  存储步长

/**贪心策略: 选择一个点,如果这个点的权值最大,必然如果他的父亲被染色了,就一定会先染色这个点。

那么这个节点就和他的父亲被绑定在一起了。

绑定以后,他们的权值=c[i]+c[fa[i]], 因为父亲先染色,孩子后染色,所以染父亲需要耗费  c[fa[i]]+2* c[i]的代价 因为是先后连续的关系

选择点的策略  有多个点绑定以后,他的节点数num[i]=与这个节点绑定的点的个数

选择 c[i]/num[i]最大的节点与他的父亲合并即可。
**/

int find(int u)
{
    if(u == pre[u])
        return u;
    return
        pre[u] = find(pre[u]);
}
int main()
{
    freopen("in.txt", "r", stdin);
    int n,r;
    while(scanf("%d%d", &n, &r), n + r)
    {
        for(int i = 1;i <= n; i++)
        {
            scanf("%d",&c[i]);
        }
        fa[r] = 0;

        int u,v;
        for(int i = 0; i < n-1; i++)
        {
            scanf("%d%d", &u, &v);
            fa[v] = u;
        }
        for(int i = 0;i <= n; i++)
            pre[i] = i,num[i]=1;
        memset(total,0,sizeof(total));

        c[0] = 0;
        for(int i = 0; i < n; i++)
        {
            u = 0;
            for(int j = 1; j <= n; j++)
            {
                if(pre[j] == j)
                {
                    // 与上面那种方法有所区别,我们采用另外一种方法求权值最大的节点信息
                    // 这种方法与上面那种方法本质是相同的
                    // 上面那种方法采用平均权值来比较
                    // 而这种方法采用直接计算权值的方法
                    if(u == 0 || c[u] * num[j] < c[j] * num[u])
                    {
                        u = j;
                    }
                }
            }
#ifdef DEBG
            printf("查找到权值最大的节点%d\n", v);
#endif
            v = find(fa[u]);
#ifdef DEBUG
            printf("查找到%d的祖宗节点%d\n\n", u, v);
#endif
            // 下面将节点u染色,然后将节点的权值和步长u并入其祖宗节点v
            total[v] += num[v] * c[u] + total[u];   // 将节点v染色
            num[v] += num[u];                       // 合并步长
            c[v] += c[u];                           // 合并权值
            pre[u] = v;                             // 将v归并为u的父节点,方便并查集
        }

        printf("%d\n",total[0]);
    }
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值