洛谷 P5290 [十二省联考2019]春节十二响 堆+启发式合并

版权声明:2333 https://blog.csdn.net/liangzihao1/article/details/89341473

题目:
https://www.luogu.org/problemnew/show/P5290

分析:
考虑一条链且根不为链端的情况。一定是根左儿子的一个数和右儿子的一个数组成一个集合。
因为显然两个节点都在一侧显然不行,只选一个太亏。
那么就是左边最大匹配右边最大,左边第二匹配右边第二……
拓展到任意子树,我们可以合并两个儿子的方案。
显然不能选两个集合都在一边(一定会有冲突,不然这两个集合一定会合并成一个集合)。
那么还是左儿子最大配上右儿子最大……,多个儿子依次合并。根节点独自开一个集合。
每次用小集合加入到大集合中,可以用堆维护大小关系。

代码:

#include <iostream>
#include <cstdio>
#include <cmath>
#include <queue>
#define LL long long

const int maxn=2e5+7;

using namespace std;

int n,x,cnt,top;
int a[maxn],ls[maxn],id[maxn],b[maxn];
LL ans;

struct edge{
    int y,next;
}g[maxn];

priority_queue <int> q[maxn];

void add(int x,int y)
{
    g[++cnt]=(edge){y,ls[x]};
    ls[x]=cnt;
}

void dfs(int x)
{
    id[x]=++cnt;
    for (int i=ls[x];i>0;i=g[i].next)
    {
        int y=g[i].y;
        dfs(y);
        if (q[id[x]].size()<q[id[y]].size()) swap(id[x],id[y]);
        top=0;
        while (!q[id[y]].empty())
        {
            b[++top]=max(q[id[x]].top(),q[id[y]].top());
            q[id[x]].pop(),q[id[y]].pop();
        }
        for (int j=1;j<=top;j++) q[id[x]].push(b[j]);
    }
    q[id[x]].push(a[x]);
}

int main()
{
    scanf("%d",&n);
    for (int i=1;i<=n;i++) scanf("%d",&a[i]);
    for (int i=2;i<=n;i++)
    {
        scanf("%d",&x);
        add(x,i);
    }
    cnt=0;
    dfs(1);
    ans=0;
    while (!q[id[1]].empty())
    {
    	ans+=(LL)q[id[1]].top();
    	q[id[1]].pop();
    }
    printf("%lld\n",ans);
}

猜你喜欢

转载自blog.csdn.net/liangzihao1/article/details/89341473
今日推荐