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

传送门

考虑一个子树里是怎么划分的,维护划分出来的每个集合的最大值,这个可以用一个 $multiset$ 维护

设 $S[x]$ 表示节点 $x$ 的子树中,最优划分 划分出来的每个块的节点最大值

首先叶子节点的集合显然只有它本身

然后考虑子树之间的合并,设两个子树根节点为 $x,y$,因为两个子树之间一定不会有祖先后代关系

贪心地想,显然 $S[x]$ 的最大值优先跟 $S[y]$ 的最大值合并(取 $max$),然后次大值跟次大值合并...这样一路合并下去是最优的

所以直接启发式合并就好了

$x$ 的子树合并完后还要考虑当前节点 $x$ 也加入进来,显然此节点只能单独分一个块出来

一开始以为复杂度 $O(nlog^2_n)$(启发式合并 $nlog_n$,$multiset$ 单次操作 $log_n$)

但是经过对代码的分析可以发现,这并不是直接合并,而是小的 $S$ 和大的取一个最大值后就没了,并没有增加大的集合的集合大小

所以每个节点只会算一次,复杂度 $O(nlog_n)$

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<cstring>
#include<set>
using namespace std;
typedef long long ll;
inline int read()
{
    int x=0; char ch=getchar();
    while(ch<'0'||ch>'9') ch=getchar();
    while(ch>='0'&&ch<='9') { x=(x<<1)+(x<<3)+(ch^48); ch=getchar(); }
    return x;
}
const int N=4e5+7;
int fir[N],from[N<<1],to[N<<1],cntt;
inline void add(int a,int b)
{
    from[++cntt]=fir[a]; fir[a]=cntt;
    to[cntt]=b;
}
int n,val[N];
multiset <int> S[N];
multiset <int>::iterator it,pit,itt;
inline void merge(int x,int y)//很多细节的启发式合并操作
{
    if(S[x].size()<S[y].size()) swap(S[x],S[y]);//启发式合并
    if(!S[y].size()) return;//记得特判,不然后面it--时会直接GG
    pit=S[x].end(); pit--; bool flag=1;
    it=S[y].end(); it--;
    while(it!=S[y].begin())
    {
        pit--; itt=pit; pit++;
        //注意要先用itt存一下pit-1的指针等等erase(pit)后pit就是指向不合法地址的指针了,那时再pit--就GG了
        if((*it)>(*pit)) S[x].erase(pit),S[x].insert(*it);
        it--; pit=itt;
    }
    if((*it)>(*pit)) S[x].erase(pit),S[x].insert(*it);//注意最后S[y].begin()的值还没合并
}
void dfs(int x)
{
    for(int i=fir[x];i;i=from[i])
        dfs(to[i]),merge(x,to[i]);
    S[x].insert(val[x]);
}
int main()
{
    n=read(); int a;
    for(int i=1;i<=n;i++) val[i]=read();
    for(int i=2;i<=n;i++)
        a=read(),add(a,i);
    dfs(1);
    ll ans=0;
    for(it=S[1].begin();it!=S[1].end();it++) ans+=(*it);
    printf("%lld",ans);
    return 0;
}

 

上一篇:CSU 2249 Altruistic Amphibians 贪心+dp


下一篇:Flume入门