1. 程式人生 > >樹染色問題color a tree(貪心)

樹染色問題color a tree(貪心)

題目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;
}