[十二省联考2019]春节十二响

一、题目

点此看题

二、解法

一道很好的子树内贪心的题目,先给出贪心方法:每个子树维护一个堆, d f s dfs dfs后合并子树的堆,合并方法就是最大的和最大的一组,次大和次大的一组,以此类推,下面从无后效性当前最优两个方面给出解释。

  • 无后效性,就是当前子树的选择不会影响到子树外的选择,可以分类讨论,如果当前合并对以后的合并没有影响,当前不合并也对以后的合并没有影响,故无后效性。
  • 当前最优,不妨设第一个堆中的最大值比第二个堆中的大,所以无论怎么合并都不会改变花费,所以直接带上最大的一个,反过来也一样,然后合并完最大的后次大的就变成最大的了,故当前最优。

正确性证毕,合并的话可以考虑启发式合并,如果子树的大小比已合并的要大,那就交换,时间复杂度就是合并中被并到一起的点数,也就是 O ( n log ⁡ n ) O(n\log n) O(nlogn),注:c++11交换 s e t set set O ( 1 ) O(1) O(1)

#include <cstdio>
#include <vector>
#include <queue>
#define LL long long
using namespace std;
const int MAXN = 200000;
int read()
{
    int num=0,flag=1;char c;
    while((c=getchar())<'0'||c>'9')if(c=='-')flag=-1;
    while(c>='0'&&c<='9')num=(num<<3)+(num<<1)+(c^48),c=getchar();
    return num*flag;
}
int n,tot,f[MAXN],a[MAXN];
priority_queue<int> h[MAXN];
vector<int> t;
struct edge
{
	int v,next;
}e[2*MAXN];
void merge(int x,int y)
{
	if(h[x].size()<h[y].size())
		swap(h[x],h[y]);
	while(!h[y].empty())
	{
		t.push_back(max(h[x].top(),h[y].top()));
		h[x].pop();h[y].pop();
	}
	while(!t.empty())
		h[x].push(t.back()),t.pop_back();
}
void dfs(int u,int fa)
{
	for(int i=f[u];i;i=e[i].next)
	{
		int v=e[i].v;
		if(v==fa) continue;
		dfs(v,u);
		merge(u,v);
	}
	h[u].push(a[u]);
}
int main()
{
	n=read();
	for(int i=0;i<n;i++)
		a[i]=read();
	for(int i=1;i<n;i++)
	{
		int j=read()-1;
		e[++tot]=edge{j,f[i]},f[i]=tot;
		e[++tot]=edge{i,f[j]},f[j]=tot;
	}
	dfs(0,0);
	LL ans=0;
	while(!h[0].empty())
		ans+=h[0].top(),h[0].pop();
	printf("%lld\n",ans); 
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值