【洛谷P5290】【LOJ3052】春节十二响【堆】
题目大意:
题目链接:
洛谷:https://www.luogu.org/problemnew/show/P5290
LOJ:https://loj.ac/problem/3052
将一棵树的每一个节点划分进一个集合,要求任意集合内的任意两点不可以是祖先 — 后代关系,每一个集合的费用为该集合内点的最大权值。求最小费用和。
思路:
我们假设一个节点有两个子节点,并且以该节点为根的子树全部只有一个子节点(该节点除外)
设黄色节点的最大值为且在以为根的子树内,那么显然是要与以为根的子树内的最大权值的节点合并的,这样显然可以减少接下来合并的费用。
那么我们将和合并时,我们对于每一个子树合并一个堆,每次将堆顶元素取出并合并为权值更大的那个。这样我们就维护好了两个子树的费用。
假设有多个子节点,那么我们每次合并两个节点,并赋值到中,所有子节点的集合合并完之后再将的权值加入堆中。
但是这样的时间复杂度很容易被卡,所以我们要用启发式合并,每次把大小相对小的往大小相对大的合并,这样每一个节点最多被合并次,时间复杂度。
代码:
#include <queue>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long ll;
const int N=200010;
int n,tot,a[N],head[N],p[N];
priority_queue<int> q[N];
ll ans;
struct edge
{
int next,to;
}e[N];
void add(int from,int to)
{
e[++tot].to=to;
e[tot].next=head[from];
head[from]=tot;
}
void merge(int &x,int &y)
{
if (q[x].size()<q[y].size()) swap(x,y);
while (q[y].size())
{
q[0].push(max(q[x].top(),q[y].top()));
q[x].pop(); q[y].pop();
}
for (;q[0].size();q[0].pop()) q[x].push(q[0].top());
}
void dfs(int x)
{
p[x]=x;
for (int i=head[x];~i;i=e[i].next)
{
dfs(e[i].to);
merge(p[x],p[e[i].to]);
}
q[p[x]].push(a[x]);
}
int main()
{
memset(head,-1,sizeof(head));
scanf("%d",&n);
for (int i=1;i<=n;i++)
scanf("%d",&a[i]);
for (int i=2,x;i<=n;i++)
{
scanf("%d",&x);
add(x,i);
}
dfs(1);
for (;q[p[1]].size();q[p[1]].pop())
ans+=q[p[1]].top();
printf("%lld\n",ans);
return 0;
}