题解 P5290 【[十二省联考2019]春节十二响】

Owen_codeisking

2019-04-07 17:50:51

Solution

不知道别人怎么写的,所以来讲一讲我自己的心路历程。 ### $n\leq 2000$ 可以贪心。先按权值从大到小排序,然后依次判一个点是否包含一个点。若没有包含关系,就直接加入集合中。 那怎么判一个点 $x$ 是否另一个点 $y$ 存在包含关系呢?$(a_x>a_y)$ 1. $x$ 在 $y$ 的子树中,即 $st_y\leq id_x\leq ed_y$,开一个单点加区间查的树状数组即可。 2. $y$ 在 $x$ 的子树中,即 $st_x\leq id_y\leq ed_x$,开一个区间加单点查的树状数组即可。 时间复杂度 $O(n^2\log n)$ 因为实际上常数根本跑不满,所以 $n\leq 2000$ 可以随便过。 ### 树是一条链($1$ 不一定是链的端点) 其实链的部分分已经提示你正解了。 容易发现 $1$ 最多有两个支链,所以我们暴力找出这两条链的所有权值,然后 $sort$ 一下,将两条支链对应的权值相加即可。 时间复杂度 $O(n\log n)$ ### 正解 借鉴目前 $loj$ 最优解 $relyt871$ 的思路。 其实链的部分分已经提示你可以尝试合并两个集合的最大值了。 我们对于每个点都开一个堆,每次将 $siz$ 较小的合并到 $siz$ 较大的堆中。 因为每个点只会被合并 $\log n$ 次,时间复杂度 $O(n\log^2 n)$ $Upd$:时间复杂度 $O(n\log n)$ [$loj$ 评测记录证明只 $merge$ 了 $n$ 次](https://loj.ac/submission/409771) $Code\ Below:$ ```cpp #include <bits/stdc++.h> #define ll long long using namespace std; const int maxn=200000+10; int n,a[maxn],fa[maxn],tmp[maxn],id[maxn],tim; int head[maxn],to[maxn],nxt[maxn],tot; priority_queue<int> q[maxn]; inline int read() { register int x=0,f=1;char ch=getchar(); while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();} while(isdigit(ch)){x=(x<<3)+(x<<1)+ch-'0';ch=getchar();} return (f==1)?x:-x; } inline void addedge(int x,int y) { to[++tot]=y; nxt[tot]=head[x]; head[x]=tot; } void dfs(int x) { id[x]=++tim; for(int i=head[x];i;i=nxt[i]) { int y=to[i];dfs(y); if(q[id[x]].size()<q[id[y]].size()) swap(id[x],id[y]); int m=q[id[y]].size(); for(int i=1;i<=m;i++) { tmp[i]=max(q[id[x]].top(),q[id[y]].top()); q[id[x]].pop();q[id[y]].pop(); } for(int i=1;i<=m;i++) q[id[x]].push(tmp[i]); } q[id[x]].push(a[x]); } int main() { n=read(); for(int i=1;i<=n;i++) a[i]=read(); for(int i=2;i<=n;i++) fa[i]=read(),addedge(fa[i],i); dfs(1); ll ans=0; while(!q[id[1]].empty()) ans+=q[id[1]].top(),q[id[1]].pop(); printf("%lld\n",ans); return 0; } ``` 暴力的 $code$ 就不给啦,有兴趣去我 $loj$ 的提交记录上看好了。