现在的位置: 首页 > 综合 > 正文

【poj 2054】 Color a Tree

2017年04月24日 ⁄ 综合 ⁄ 共 2671字 ⁄ 字号 评论关闭

题意:

有一棵树,需要给其所有节点染色,每个点染色所需的时间是一样的都是1.给每个点染色,还有一个开销“当前时间×ci”,ci是每个节点的一个权值。 
染色还有另一个约束条件,要染一个点必须要先染好其父节点,所以第一个染的点是根节点。 

求最小开销。


解法:

小班课比较详细地给出了解法和证明。 
(以下已经把每个点看成一个点集,独立的一个点可看做只有一个元素的点集,每个点集中的点有顺序,并且需要依次连续地染色,也就是染完第一个点后,需要立刻依次染之后的点) 
每次找一个最大权值的非根点集,将它和它的父点集合并。新的点集的权值是(新点集中所有点的权值和)/(新点集中点的个数)。这里点集中的点有顺序,合并时,子点集的点放在父点集的点后面。 
进行上述步骤,直到只剩下一个根点集。这时候,根点集中点的顺序,就是染色的顺序。



证明:

(i)先看每个都是点的情况。给出一个结论:如果这棵树中有最大权值点x(非根),那么一旦x的父节点已经染色,就应该立刻染x。 
这比较好证:设x的父节点为y,染色顺序为ya...bx,a...b这一段是别的一些可以染的点。这个染色顺序的意思是说染完x的父节点y之后,先染了一些别的点,再染x。 
如果我们把b和x的染色顺序交换,显然是可行的,并且会更优:比较xb和bx,使用前者我们的收益(少掉的开销)是Cx-Cb,x少等了1的时间,b多等了1的时间。显然,收益Cx-Cb是正的,因为x的权值是当前最大的。这样我们也可以证,染完y之后应该立刻染x,所以可以把y和x合并成一个点集。 
(ii)现在看都看做点集的情况,证明合并后以平均权值作为新的权值是合理的。 
一个结论:如果这棵树中有最大平均权值点集X(非根),那么一旦X的父点集已经染色,就应该立刻染X。

这和(i)类似:设X的父子集Y,染色顺序YA...BX,A...B是别的一些可以染的点集。下证B和X染色顺序互换可以更优。设B的总权值为SB,点数为NB;X的为SX和NX。XB和BX比较,使用前者的收益是SX×NB- SB×NX,这相当与X中的每个点都少等待NB时间,而B中的每个点都多等了NX的时间。又因为X是最大平均权值点集,所以权值SX/NX>SB/NB,所以可以确保收益SX×NB-SB×NX是正的。


#include <cstdio>
#include <cstdlib>
#include <string>
#include <cstring>
#include <vector>
#define maxn 1005
using namespace std;
 
class node
{
    public:
        int value_sum;//点集中所有点的权值和
        int num;//点集中点的个数
        int father;
        int set_id;//所属点集编号
        bool is_setrep;//是不是点集的首个元素
        double value;//权值平均
        vector<int> in_set;//点集中其他点
        vector<int> son;//该点集的子点集
 
        node()
        {
            num = 1;
            father = -1;
            is_setrep = true;
        }
};
 
node no[maxn];
int seq[maxn] = {0};//最后的涂的顺序的序列
int c[maxn] = {0};//涂每个点的花费
 
int n = 0, r = 0;
 
void init()
{
    memset(no, 0, sizeof(no));
    for (int i = 1; i <= n; ++i)
    {
            no[i].num = 1;
            no[i].father = -1;
            no[i].is_setrep = true;
    }
}
 
//将编号为a的点集与其父点集合并
void union_node(int a)
{
    int fa = no[a].father;
    int son_num = no[a].son.size();
    //printf("sonnum: %d\n", son_num);
    no[fa].value_sum += no[a].value_sum;
    no[fa].num += no[a].num;
    no[fa].value = (double)(no[fa].value_sum) / no[fa].num;
    no[a].set_id = fa;
    no[a].is_setrep = false;
    no[fa].in_set.push_back(a);
    //printf("value: %f\n", no[fa].value);
    for (int i = 0; i < no[a].num - 1; ++i)
    {
        no[fa].in_set.push_back(no[a].in_set[i]);
        no[no[a].in_set[i]].set_id = fa;
    }
    for (int i = 0; i < son_num; ++i)
    {
        no[no[a].son[i]].father = fa;
        no[fa].son.push_back(no[a].son[i]);
    }
}
 
int main()
{
    while(true)
    {
        scanf("%d%d", &n, &r);
        init();
        if (n == 0)
            break;
        int tmp = 0;
        for (int i = 1; i <= n; ++i)
        {
            scanf("%d", &tmp);
            c[i] = tmp;
            no[i].value = no[i].value_sum = tmp;
            no[i].set_id = i;
        }
 
        int tmp1 = 0, tmp2 = 0;
        for (int i = 0; i < n - 1; ++i)
        {
            scanf("%d%d", &tmp1, &tmp2);
            if(tmp1 == 0)
                break;
            no[tmp1].son.push_back(tmp2);
            no[tmp2].father = tmp1;
        }
 
        int cur_num = n;
        while(true)
        {
            if (cur_num == 1)
                break;
            double max_v = 0;
            int max_i = 0;
            for (int i = 1; i <= n; ++i)
            {
                //每次找不是根节点的最大权值子集与其父子集合并
                if (no[i].is_setrep && i != r && max_v < no[i].value)
                {
                    max_v = no[i].value;
                    max_i = i;
                }
            }
            //printf("%d\n", max_i);
            union_node(max_i);
 
            --cur_num;
        }
 
        seq[1] = c[r];
        for (int i = 0; i < n - 1; ++i)
        {
            seq[i + 2] = c[no[r].in_set[i]];
        }
 
        int ans = 0;
        for (int i = 1; i <= n; ++i)
        {
            ans += i*seq[i];
        }
        printf("%d\n", ans);
    }
    return 0;
}


所以这样合并没有问题。

所以这样合并没有问题。

抱歉!评论已关闭.