POJ 2054 Color a Tree#贪心(难,好题)

题目链接

代码借鉴此博:http://www.cnblogs.com/vongang/archive/2011/08/19/2146070.html

其中关于max{c[fa]/t[fa]}贪心原则,此博有很好的解释:http://www.cnblogs.com/rainydays/p/3271277.html

在此引用其中几段话:

试想,如果没有父节点排在节点之前的限制,那么这个题目非常简单,只需要将结点按照权值从大到小排列即可。加上了这个限制之后,如果权值最大的那个节点一旦满足了条件(父节点被排在了之前的某个位置),那么这个权值最大的节点一定要紧挨着这个父节点,即把这个权值最大的节点排在它所能排的最前面的位置。因为对于这个节点如果不受限制应该排在第一位,而有了限制,在满足了限制之后也应把它尽可能地排在前面。所以它一定是挨着父节点的。那么现在在最终的排列中我们确定了两个节点的前后相邻关系,将他们绑定在了一起。

试想如果保持这个相邻关系的同时去掉其他节点的限制,那么我们应该如何排列呢?我们假设绑定在一起的两节点是a和b。现有一个另外的节点x,我们看两种排列xab,abx对最终的计算结果有什么影响。x*i+a*(i+1)+b*(i+2); a*i + b*(i+1) + x*(i+2)。后者减去前者等于2x-(a+b)。即将x从ab之前挪到ab之后,ab各左移1位,结果减小a+b。x右移2位结果增加2x。因此两者谁在前谁在后我们只需要比较a+b和2x即可,也可以比较(a+b)/2和x。

将这个定理进行一下推广,绑定在一起的不一定是两个节点,可以是一个更长的序列,与这个序列进行比较看谁放在前面的也可以是一个序列。设一个序列有n1个节点,第二个序列有n2个节点。那么我们比较两者谁放在前面的时候需要比较的是(n1个权值之和×n2)和(n2个权值之和×n1)。即左移和右移产生的结果变化。当然也可以比较(n1个权值之和/n1)和(n2个权值之和/n2)。

我们可以再次进行推广,如果我们要排列的不是节点,而是许多序列的话,那么我们只需要计算每个序列权值的平均数(例如:n个节点的序列,要计算n个权值之和/n),然后按照这个平均数从大到小排列即可使得计算结果最小。这样就可以让序列与节点有了一个统一的衡量值——平均数。

这样一来,我们就可以将上面的绑定两节点的操作看成是将问题规模缩小的操作,在帮定两节点的同时我们在树中也将两节点合并,变为一个节点,即将子节点的孩子变为父节点的孩子。然后合并后的节点的权值是合并在这个节点中的所有节点的权值的平均数。我们成功的将问题规模减小了1。只需要不断这样做即可将问题缩减为只有一个节点。



以下为AC code:

//贪心:如果这棵树中有最大权值点X(非根),
//那么一旦X的父节点Y已经染色,就应该立刻染X
//于是X和Y合并成一个点集,
//新点集的权值=(新点集中所有点的权值和)/(新点集中点的个数)
//类似的,该点集可以看为一个点
//重复上述贪心思路,直到最后只剩下一个根r点集
#include<iostream>
#include<cstdio>
#include<cmath>
#include<algorithm>
#include<cstring>
using namespace std;

const int N=1005;

struct Node
{
    int f;//父结点
    int t;//时间
    int c;//原权值
    double w;//贪心权值(c/t)
}node[N];

int n,r;

int findPos()
{
    int pos;
    double wmax=0;
    for(int i=1;i<=n;i++)
        if(node[i].w>wmax&&i!=r)
        {
            wmax=node[i].w;
            pos=i;
        }
    return pos;
}

int main()
{
    while(scanf("%d%d",&n,&r)&&n+r)
    {
        int pos,fa,res=0;
        for(int i=1;i<=n;i++)
        {
            scanf("%d",&node[i].c);
            node[i].w=node[i].c;//将贪心权值w初始化为原权值c
            node[i].t=1;//时间初始化为1
            res+=node[i].c;//结果初始化为sum of所有原权值c
        }
        int u,v;
        for(int i=1;i<n;i++)
        {
            scanf("%d%d",&u,&v);
            node[v].f=u;//记录父结点
        }

        for(int i=1;i<n;i++)
        {
            pos=findPos();//找到贪心权值最大的位置
            node[pos].w=0;//置0,以便下次查找时跳过之
            fa=node[pos].f;//fa为pos的父结点
            res+=node[pos].c*node[fa].t;//res+=pos原权值*fa父结点时间(node[fa].t实际上代表,点集中有几个点)
            for(int j=1;j<=n;j++)
                if(node[j].f==pos)
                    node[j].f=fa;//若有j的父结点是pos,则将j的父结点改为fa,建立新集合
            node[fa].t+=node[pos].t;//更新以fa为父结点的点集内点的个数(即加上以pos为父结点的点集内点的个数)
            node[fa].c+=node[pos].c;//更新点集的话,那权值自然也要更新
            node[fa].w=(double)node[fa].c/node[fa].t;//贪心原则:新点集权值和/新点集中点的个数
        }
        printf("%d\n",res);
    }
    return 0;
}
时间: 2024-10-19 00:03:28

POJ 2054 Color a Tree#贪心(难,好题)的相关文章

poj 2054 Color a Tree(贪心)

# include <stdio.h> # include <algorithm> # include <string.h> using namespace std; int father[1010]; int next[1010];//当前集合的下个元素(包括i) int pre[1010];//当前集合的上个元素(包括i) int num[1010];//num[i]当前集合储存的点的个数(包括i) int vis[1010]; int sum[1010];//当前

poj 2054 Color a Tree 据说是贪心

Color a Tree Time Limit: 1000MS   Memory Limit: 30000K Total Submissions: 7112   Accepted: 2438 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&qu

hdu 1055 &amp; poj 2054 Color a Tree 树&amp;贪心 找最大费用点和父节点合并

Color a Tree Time Limit: 1000MS Memory Limit: 30000K Total Submissions: 7144 Accepted: 2458 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"

poj 2054 Color a Tree

很有意思的一道贪心题,自己没想出来,理解了一下别人的思路 参考:http://www.cnblogs.com/yu-chao/archive/2012/02/19/2358565.html http://my.oschina.net/locusxt/blog/210536 题目大意:给你一颗树,树上每个节点都有自己的权值,现在要把这棵树上的所有节点染上颜色,每染一个节点需要一个单位的时间,染一个点的花费是该点的权值乘以当前时间(时间从1开始).规定在染当前点之前,必须先染他的父亲节点,求最小的花

poj 2054 Color a Tree(贪婪)

# include <stdio.h> # include <algorithm> # include <string.h> using namespace std; int father[1010]; int next[1010];//当前集合的下个元素(包含i) int pre[1010];//当前集合的上个元素(包含i) int num[1010];//num[i]当前集合储存的点的个数(包含i) int vis[1010]; int sum[1010];//当前

HDU 1055 Color a Tree

题目:Color a Tree 链接:http://acm.hdu.edu.cn/showproblem.php?pid=1055 题意:给一棵树,要给树上每一个结点染色,第i 个结点染色需要代价为:t * w[i] (t 表示i 结点是第几个染色的),还有一个前提是:要给i 结点染色,必须先给i 结点的父结点染色(根结点随便染). 思路: 贪心. 假定当前未染色的结点中权值最大的是A结点,如果A结点上方都染完色,那么现在一定是对A结点进行染色. 理解如下: 假设在上述情况下先对其他结点染色,在

POJ 2965-The Pilots Brothers&#39; refrigerator(贪心+枚举)

The Pilots Brothers' refrigerator Time Limit: 1000MS   Memory Limit: 65536K Total Submissions: 19464   Accepted: 7462   Special Judge Description The game "The Pilots Brothers: following the stripy elephant" has a quest where a player needs to o

codeforces219C - Color Stripe DP+贪心

题意:给你n,k,大意就是说给你一个已经涂满颜色长为n的字符串,现有k种颜色可以选择,问你最少要改变多少个箱子的颜色使得相邻箱子之间颜色不同. 解题思路:当k = 2 时单独讨论,不能用贪心,其余情况都可贪心得到. 解题代码: 1 // File Name: 219c.cpp 2 // Author: darkdream 3 // Created Time: 2014年07月26日 星期六 15时45分56秒 4 5 #include<vector> 6 #include<list>

hdu 4603 Color the Tree 2013多校1-4

这道题细节真的很多 首先可以想到a和b的最优策略一定是沿着a和b在树上的链走,走到某个点停止,然后再依次占领和这个点邻接的边 所以,解决这道题的步骤如下: 预处理阶段: step 1:取任意一个点为根节点,找出父子关系并且对这个树进行dp,求出从某个节点出发往下所包含的所有边的权值总和  复杂度O(n) step 2:从tree dp 的结果中计算对于某个节点,从某条边出发所包含的边的综合,并且对其从大到小进行排序 复杂度O(n*logn) step 3:dfs求出这颗树的欧拉回路,以及每个点的