DestinHistoire

 

BZOJ-1908 Pku2054 Color a Tree(贪心)

题目描述

  一棵有 \(n(1\leq n\leq 1000)\) 个节点的树,每个节点 \(i(1\leq i\leq n)\) 都有一个权值 \(A[i](1\leq A[i]\leq 1000)\)。现在要把这棵树的节点全部染色,染色的规则是:根节点 \(R\) 可以随时被染色;对于其他节点,在被染色之前它的父节点必须已经染上了色。每次染色的代价为 \(T\times A[i]\),其中 \(T\) 代表当前是第几次染色。求把这棵树染色的最小总代价。

分析

  有一个错误的贪心策略是:每一步在可以被染色的点里选权值最大的染色,很容易构造出反例:构造一棵树,一个权值很小的节点下边有很多权值巨大的节点,另一个权值较大的节点却没有子节点。

  可以发现一个性质:树中除根节点外权值最大的点,一定会在它的父节点被染色后立即染色。

  于是我们可以确定的是,树中权值最大的点及其父节点的染色操作是连续进行的,可以把这两个点合并起来。合并得到的新点的权值设为这两个点的权值的平均值。

  例如有权值为 \(x,y,z\) 的三个点,我们已知 \(x\)\(y\) 的染色操作是连续进行的,那么就有两种可能的染色方案:

  \(1.\) 先染 \(x,y\),再染 \(z\),代价是 \(x+2y+3z\)

  \(2.\) 先染 \(z\),再染 \(x,y\),代价是 \(z+2x+3y\)

  我们主要关心这两个代价之间的大小关系,所以不妨把两个式子同时加上 \((z-y)\) 再除以 \(2\),分别得到:

  \(1.\) 代价 \((x+y)/2+2\times z\)

  \(2.\) 代价 \(z+2\times ((x+y)/2)\)

  这恰好就相当于有权值为 \((x+y)/2\)\(z\) 的两个点的两种染色次序。换言之,下列两种情况的最优染色次序可以相互转化:

  \(1.\) 权值为 \(x,y,z\) 的三个点。

  \(2.\) 权值为 \((x+y)/2\)\(z\) 的两个点。

  因此可以得到一种等效权值的算法:记录每个点是由多少个点合并而成的,一个点的等效权值定义为:该点包含的原始权值总和除以该点包含的原始点数。

  不断在树中取等效权值最大的点 \(p\),与其父节点 \(fa\) 合并。合并之前 \(p\)\(fa\) 各自包含的点的染色顺序是已知的,我们就让 \(p\) 中第一个点排在 \(fa\) 中最后一个点之后紧接着被染色,把这个顺序保存在 \(p\)\(fa\) 合并以后的点上。最终整棵树合并成一个点后,我们就按照这个点内保存的顺序在原始的树上把各个节点依次染色,计算出花费的总代价,即为所求。

代码

#include<bits/stdc++.h>
using namespace std;
struct node
{
    int sum;
    int ans;
    int cnt;
}tree[1010];
int fa[1010];
bool vis[1010];
int get(int x)
{
    if(x!=fa[x]&&vis[fa[x]]==1)
        return fa[x]=get(fa[x]);
    return fa[x];
}
int main()
{
    int n,root;
    while(cin>>n>>root&&n&&root)
    {
        memset(tree,0,sizeof(tree));
        for(int i=1;i<=n;i++)
        {
            scanf("%d",&tree[i].sum);
            tree[i].ans=tree[i].sum;
            tree[i].cnt=1;
        }
        memset(fa,0,sizeof(fa));
        memset(vis,0,sizeof(vis));
        fa[root]=root;
        for(int i=1;i<=n-1;i++)
        {
            int x,y;
            scanf("%d %d",&x,&y);
            fa[y]=x;
        }
        int pos=-1;
        for(int i=1;i<=n-1;i++)
        {
            double maxn=0;
            for(int j=1;j<=n;j++)
            {
                if(vis[j]||j==root)
                    continue;
                double temp=1.0*tree[j].sum/tree[j].cnt;
                if(temp>maxn)
                {
                    maxn=temp;
                    pos=j;
                }
            }
            vis[pos]=1;
            int father=get(pos);
            tree[father].ans=tree[father].ans+tree[pos].ans+tree[father].cnt*tree[pos].sum;
            tree[father].sum=tree[father].sum+tree[pos].sum;
            tree[father].cnt=tree[father].cnt+tree[pos].cnt;
        }
        cout<<tree[root].ans<<endl;
    }
    return 0;
}

posted on 2020-11-27 14:32  DestinHistoire  阅读(92)  评论(0)    收藏  举报

导航