【洛谷P5290】【LOJ3052】春节十二响【堆】

题目大意:

题目链接:
洛谷:https://www.luogu.org/problemnew/show/P5290
LOJ:https://loj.ac/problem/3052
将一棵树的每一个节点划分进一个集合,要求任意集合内的任意两点不可以是祖先 — 后代关系,每一个集合的费用为该集合内点的最大权值。求最小费用和。


思路:

我们假设一个节点有两个子节点,并且以该节点为根的子树全部只有一个子节点(该节点除外)
在这里插入图片描述
设黄色节点的最大值为maxmax且在以xx为根的子树内,那么xx显然是要与以yy为根的子树内的最大权值的节点合并的,这样显然可以减少接下来合并的费用。
那么我们将xxyy合并时,我们对于每一个子树合并一个堆,每次将堆顶元素取出并合并为权值更大的那个。这样我们就维护好了两个子树的费用。
假设rootroot有多个子节点,那么我们每次合并两个节点,并赋值到rootroot中,所有子节点的集合合并完之后再将rootroot的权值加入堆中。
但是这样的时间复杂度很容易被卡,所以我们要用启发式合并,每次把大小相对小的往大小相对大的合并,这样每一个节点最多被合并lognlog n次,时间复杂度O(nlogn)O(nlog n)


代码:

#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
",ans);
	return 0;
}
原文地址:https://www.cnblogs.com/hello-tomorrow/p/11998086.html