一、题目
二、解法
一道很好的子树内贪心的题目,先给出贪心方法:每个子树维护一个堆, 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);
}