版权声明: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);
}