bzoj4919: [Lydsy1706月赛]大根堆
题目描述:
给定一棵 $n$ 个节点的有根树,编号依次为 $1$ 到 $n$ ,其中1号点为根节点。每个点有一个权值 $v_i$ 。
你需要将这棵树转化成一个大根堆。确切地说,你需要选择尽可能多的节点,满足大根堆的性质:对于任意两个点i,j,如果i在树上是j的祖先,那么 $v_i>v_j$ 。
请计算可选的最多的点数,注意这些点不必形成这棵树的一个连通子树。
算法标签:主席树,启发式合并
思路:
这道题相当于在树上的最长上升子序列,考虑父亲与孩子和同一个父亲的孩子之间对答案的影响。
将权值离散化,在每一个节点建成权值为下标的线段树。
首先考虑父亲与孩子,选中这个孩子的答案可以对于权值大于自己的父亲造成贡献,所以我们在线段树中在 $[val[x]+1,mx]$ 这个区间更新当前答案。
孩子之间没有影响,所以我们直接对应权值的答案相加即可。
以下代码:
#include<bits/stdc++.h> #define il inline #define _(d) while(d(isdigit(ch=getchar()))) using namespace std; const int N=2e5+5; int n,val[N],rt[N],head[N],ne[N],to[N],cnt,b[N],nt,tot; struct node{ int l,r,num; }t[N*40]; il int read(){ int x,f=1;char ch; _(!)ch=='-'?f=-1:f;x=ch^48; _()x=(x<<1)+(x<<3)+(ch^48); return f*x; } il void ins(int x,int y){ ne[++cnt]=head[x]; head[x]=cnt;to[cnt]=y; } il void modify(int &x,int y,int l,int r){ if(!x||!y){x=x+y;return;} t[x].num+=t[y].num; if(l==r)return;int mid=(l+r)>>1; modify(t[x].l,t[y].l,l,mid); modify(t[x].r,t[y].r,mid+1,r); } il void insert(int &x,int l,int r,int ql,int qr){ if(!x)x=++nt; if(ql<=l&&r<=qr){t[x].num++;return;} int mid=(l+r)>>1; if(ql<=mid)insert(t[x].l,l,mid,ql,qr); if(mid<qr)insert(t[x].r,mid+1,r,ql,qr); } il int query(int x,int l,int r,int pos){ if(!x||!pos)return 0; if(l==r)return t[x].num;int mid=(l+r)>>1; if(pos<=mid)return t[x].num+query(t[x].l,l,mid,pos); return t[x].num+query(t[x].r,mid+1,r,pos); } il void dfs(int x){ for(int i=head[x];i;i=ne[i]){ dfs(to[i]); if(t[rt[to[i]]].num>t[rt[x]].num)swap(rt[x],rt[to[i]]); modify(rt[x],rt[to[i]],1,tot); } int res=query(rt[x],1,tot,val[x]-1)+1; if(query(rt[x],1,tot,val[x])>=res)return; int l=val[x],r=tot,ret=l; while(l<=r){ int mid=(l+r)>>1; if(query(rt[x],1,tot,mid)<res)l=mid+1,ret=mid; else r=mid-1; } insert(rt[x],1,tot,val[x],ret); } int main() { n=read();val[1]=b[1]=read();read(); for(int i=2;i<=n;i++){ b[i]=val[i]=read();ins(read(),i); } sort(b+1,b+1+n);tot=unique(b+1,b+1+n)-b-1; for(int i=1;i<=n;i++)val[i]=lower_bound(b+1,b+1+tot,val[i])-b; dfs(1); printf("%d\n",query(rt[1],1,tot,cnt)); return 0; }