目录
显示
题目链接
https://www.luogu.org/problem/P5290
题解
我们从一条链的特殊数据下手。
注意到 \(1\) 号点不一定是链的一端,这就意味着 \(1\) 号点可能会最多有两个子树,而这两个子树又都是一条链。
我们可以采用这样的贪心策略:首先将 \(1\) 号点单独分为一段(\(1\) 号点不能和其他点同时在一个段中)。接下来对每个链分别维护一个大根堆,每次取出两个堆中的最大值分成一段,并分别弹出两个堆的堆顶,直到其中一个堆空为止。
剩下一个堆中的任意两个元素都不能同时在一个段中,我们对每个元素新开一个段即可。
接下来将问题扩展到一般的树。
类似上面的方法,我们对每个节点维护一个堆。刚开始时,每个堆里只有该节点的值。
接下来从根节点开始进行 dfs。
每次遍历完一个子树后,我们就将当前节点与子节点的堆进行一轮合并:弹出两个堆的堆顶,并将两者最大值加入新堆即可。一个堆空的时候,就把另一个堆的剩余元素全部加入新堆。
怎么合并效率最高呢?启发式合并!将小的堆合并到大的堆就可以了。
该算法的复杂度是多少呢?因为每个点最多只会弹出一次,时间复杂度是 \(O(n \log n)\) 的。(具体可以参考 Topcarry 的题解)
#include <iostream>
#include <algorithm>
#include <queue>
using namespace std;
struct edge
{
int v,next;
}e[200005];
int head[200005],m[200005],cnt;
priority_queue<int> q[200005];
void addedge(int u,int v)
{
e[++cnt].v=v;
e[cnt].next=head[u];
head[u]=cnt;
}
void merge(int x,int y)
{
if(q[x].size()<q[y].size())swap(q[x],q[y]);
priority_queue<int> tmp;
while(!q[y].empty())
{
tmp.push(max(q[x].top(),q[y].top()));
q[x].pop(),q[y].pop();
}
while(!tmp.empty())
{
q[x].push(tmp.top());
tmp.pop();
}
}
void dfs(int u)
{
for(int i=head[u];i;i=e[i].next)
dfs(e[i].v),merge(u,e[i].v);
q[u].push(m[u]);
}
int main()
{
int n;
cin>>n;
for(int i=1;i<=n;i++)
cin>>m[i];
for(int i=2;i<=n;i++)
{
int f;
cin>>f;
addedge(f,i);
}
dfs(1);
long long ans=0;
while(!q[1].empty())
ans+=q[1].top(),q[1].pop();
cout<<ans<<endl;
return 0;
}