题意:
给你一棵
个点的有根树,根是
号节点,每个节点有一个权值,你要把所有点划分成若干个集合,每个集合中的点在树上不能有父子关系,每个集合的权值是所有集合中的点权值最大的那个。问所有集合的最小权值和是多少。
。
题解:
送我退役的第三道题。
感觉除了一些复杂度完全没法优化的暴力之外,基本都是要基于一些贪心的思想来做。我现在知道两种做法,一种是正解的思路,一种是他们当场想出的一种思路。
先说说正解的思路。正解的关键是考虑从下向上合并子树的过程答案的变化。我们发现,对于两棵子树,我们都把子树中的点从大到小排好序之后,全局最大值一定会累加进答案,那么为了让总答案最小,我们一定对尽可能的消去另一个子树中最大的那个点。这样我们维护一个子树内的权值和,然后每次合并子树的时候,从大到小拿出每一个点,然后对应合并,保留权值大的那个,对应位置权值小的那个就和权值大的那个可以放在一个集合,不会统计到答案中了。我们发现维护每个子树的大小顺序可以用一个堆或者multiset来做,在合并的时候要启发式合并,这里要好好思考一下写法,以免TLE或者MLE,我现在堆的写法还有点问题,于是代码先用一个multiset的写法了。当然用左偏树写是可以一个 的。
再说一下第二种思路。第二种思路是,我们每次先找出全局最大的数,这个数成为这一次的答案,然后它子树内的点和它到根的路径上的点都不能选了。然后我们每次贪心地在其他可以选的点中选权值最大的,直到整个子树没有可以选的点,那么刚才的那些点就成为一个集合。我们用树剖来维护之前的过程,大体上要维护一个区间打不可以被选的标记、整体删除标记、单点修改、区间最大值之类的操作。具体细节我也没太仔细想,但是考场是有人用这个写法过掉了的。这样每个点是只会被拿出来一次的,于是复杂度是对的,是两个 。
这题数据不是很卡,反正据我所知各种一个 和两个 的写法都能过。
代码:
#include <bits/stdc++.h>
using namespace std;
int n,val[200010],hed[200010],cnt,fa[200010],QAQ[200010],shu,book[200010];
struct node
{
int to,next;
}a[400010];
multiset<int> s[200010],ji;
long long res[200010];
inline int read()
{
int x=0;
char s=getchar();
while(s>'9'||s<'0')
s=getchar();
while(s>='0'&&s<='9')
{
x=x*10+s-'0';
s=getchar();
}
return x;
}
inline void add(int from,int to)
{
a[++cnt].to=to;
a[cnt].next=hed[from];
hed[from]=cnt;
}
inline void dfs(int x)
{
int pd=0;
for(int i=hed[x];i;i=a[i].next)
{
int y=a[i].to;
dfs(y);
res[x]+=res[y];
if(!pd)
{
pd=1;
book[x]=book[y];
continue;
}
if(s[book[x]].size()<s[book[y]].size())
swap(s[book[x]],s[book[y]]);
while(!s[book[y]].empty())
{
int u=*(--s[book[x]].end()),v=*(--s[book[y]].end());
res[x]-=min(u,v);
ji.insert(max(u,v));
s[book[x]].erase(s[book[x]].find(u));
s[book[y]].erase(s[book[y]].find(v));
}
while(!ji.empty())
{
int qwq=*(--ji.end());
s[book[x]].insert(qwq);
ji.erase(ji.find(qwq));
}
s[book[y]].clear();
}
if(!book[x])
book[x]=x;
s[book[x]].insert(val[x]);
}
int main()
{
n=read();
for(int i=1;i<=n;++i)
{
val[i]=read();
res[i]=val[i];
}
for(int i=2;i<=n;++i)
{
fa[i]=read();
add(fa[i],i);
}
dfs(1);
printf("%lld\n",res[1]);
return 0;
}