hdu6060 RXD and dividing

先说几句题外话,这题是17年多校联合训练赛的一题,根据去年的榜单,大部分队都有出这题。现在把这题补一下。

看了一下官方的题解,这题好像是套了一个数据结构叫“斯坦纳树”,网上关于这个数据结构的介绍以及题好像不是很多,有兴趣的可以自己了解一下。

进入正题,先给出题意。

给出n个结点和n-1条边,每条边都有权值,保证形成最小生成树。然后题目要求你把2-n里的所有结点分为k个集合,每个集合不相交且并集仍然是{2,3...n}。然后每次取出一个集合里所有点,与点1形成连通分量,把连通分量里的所有边权相加以后,加到ans上,问最后的ans最大值是多少。

题目所给的样例

上面的图是给出的一组样例(作图比较丑,也没有找到合适的作图工具)

样例是要求把,2-5分成4个集合,答案应该是27。因为要让ans尽可能大,所以我们可以采取贪心的策略,考虑每条边对于最后的ans的贡献。我们应该让一个结点的子孙尽量分在不同的集合中,这样这个结点它自身和它的父亲结点所连的这条边就可以多走x次(x是它的子孙的数量)

就拿上图来说,3,4,5应该分在3个集合中,这样1到2的这条边就可以多走3次,2这个结点也应该尽量不和它的儿子在一个集合中。当然,还应该考虑k比结点子孙数量小的情况,所以一条边对于ans的贡献值应该是min(k,k的子孙的数量)*边权。

求某个结点的子孙,就是求这个树的结点的子树数量,跑一下dfs就可以了。

补充一点:我发现这个题输入的第一个数一定是第二个数的父亲,也就是输入顺序是(父亲-儿子)这样的顺序,我的代码就是按这样的顺序输入的,要是反序得到的ans就不同了,但是题目中并没有说明是按这样的顺序输入的,只说明了序号u和序号v形成一条边,并没有说明哪个是树上的父节点。但是我的代码也过了(23333),不知道是不是出题人忘了这个说明,还是没想到这一点。(还有就是输入的时候scanf前面没有加~结果T了好几发)

#include<cstdio>
#include<algorithm>
#include<vector>
#include<iostream>
#include<queue>
#include<cstring>
#include<cmath>
using namespace std;

typedef long long ll;
const int maxn=1e6+5;

struct node
{
    int next,w;

};
vector<node>vec[maxn];
node tem;
int son[maxn],p[maxn];

void dfs(int v,int u)
{
    son[v]=1;
    int len=vec[v].size();
    for(int i=0; i<len; i++)
    {
        int t=vec[v][i].next;
        p[t]=vec[v][i].w;
        dfs(t,v);
        son[v]+=son[t];
    }
}

int main()
{
    int n,k,u,v,value;
    while(~scanf("%d%d",&n,&k))
    {
        for(int i=1; i<=n; i++)
        {
            vec[i].clear();
            son[i]=0;
            p[i]=0;
        }
        for(int i=1; i<=n-1; i++)
        {
            scanf("%d%d%d",&u,&v,&value);
            tem.next=v;
            tem.w=value;
            vec[u].push_back(tem);
        }
        dfs(1,0);
        ll ans=0;
        for(int i=2;i<=n;i++)
        {
            ans+=(ll)p[i]*min(son[i],k);
        }
        printf("%lld\n",ans);
    }
    return 0;
}

猜你喜欢

转载自blog.csdn.net/zero___zero/article/details/81102130