BZOJ4919 大根堆(线段树合并)

题目描述

给定一个 n n n个节点的的有根树,编号依次为 1 1 1 n n n,其中 1 1 1号节点为根节点。每个点有一个权值 v i v_i vi

你需要将这棵树转化为一个大根堆。确切地说,你需要找到尽可能多的节点,满足大根堆的性质:对于任意两个点 i , j i,j i,j,如果 i i i在树上是 j j j的祖先,那么 v i > v j v_i>v_j vi>vj

请计算可选的最多的点数,注意这些点不必形成这棵树的一个连通子树。

输入样例
6
3 0
1 1
2 1
3 1
4 1
5 1
输出样例
5
数据范围
  • 1 ≤ n ≤ 2 × 1 0 5 1\leq n\leq 2\times 10^5 1n2×105
  • 0 ≤ v i ≤ 1 0 9 , 1 ≤ p i < i 0\leq v_i\leq 10^9,1\leq p_i< i 0vi109,1pi<i

题解

前置知识:线段树合并

这道题其实就是让我们求树上的最长上升子序列

首先,我们先考虑一个序列的最长上升子序列。假设已经求出了前 i i i个数的最长上升子序列,在加入第 i + 1 i+1 i+1个数时,在当前数列中找到第一个大于等于它的数。如果有,则用新加入的数替换;否则将新加入的数放在队尾。

树上呢?也一样。用权值线段树维护每一个点,线段树的 s i z siz siz值就是该点所在子树中能选的最多的点的数量。

对于每个节点,先遍历其子树,然后将该节点的儿子节点的线段树合并到自己的线段树上。最后,在自己的线段树上找到第一个大于等于该节点的权值的位置。如果有,在权值线段树上这个位置减一,在该节点的权值所在位置加一;否则直接在该节点权值所在的位置加一。

最后,根节点的线段树的大小就是答案。时间复杂度为 O ( n log ⁡ n ) O(n\log n) O(nlogn)

code

#include<bits/stdc++.h>
#define N 200000
using namespace std;
int n,x,tot=0,f,v[200005],num[200005],d[400005],l[400005],r[400005],rt[200005];
struct node{
    int lc,rc,s;
}tr[10000005];
void add(int xx,int yy){
    l[++tot]=r[xx];d[tot]=yy;r[xx]=tot;
}
void pt(int &k,int l,int r,int z){
    if(!k) k=++tot;
    if(l==r){
        ++tr[k].s;return;
    }
    int mid=(l+r)/2;
    if(z<=mid) pt(tr[k].lc,l,mid,z);
    else pt(tr[k].rc,mid+1,r,z);
    tr[k].s=tr[tr[k].lc].s+tr[tr[k].rc].s;
}
void merge(int &r1,int r2,int l,int r){
    if(!r1||!r2){
        r1=r1+r2;return;
    }
    if(l==r){
        tr[r1].s+=tr[r2].s;return;
    }
    int mid=(l+r)/2;
    merge(tr[r1].lc,tr[r2].lc,l,mid);
    merge(tr[r1].rc,tr[r2].rc,mid+1,r);
    tr[r1].s=tr[tr[r1].lc].s+tr[tr[r1].rc].s;
}
void dele(int k,int l,int r,int z){
    if(!k) return;
    if(l==r){
        if(tr[k].s>0){
            --tr[k].s;f=1;
        }
        return;
    }
    int mid=(l+r)/2;
    if(z<=mid){
        dele(tr[k].lc,l,mid,z);
        if(!f) dele(tr[k].rc,mid+1,r,z);
    }
    else dele(tr[k].rc,mid+1,r,z);
    tr[k].s=tr[tr[k].lc].s+tr[tr[k].rc].s;
}
void dfs(int u){
    for(int i=r[u];i;i=l[i]){
        dfs(d[i]);
        merge(rt[u],rt[d[i]],1,N);
    }
    f=0;
    dele(rt[u],1,N,v[u]);
    pt(rt[u],1,N,v[u]);
}
int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++){
        scanf("%d%d",&v[i],&x);num[i]=v[i];
        if(x) add(x,i);
    }
    tot=0;
    sort(num+1,num+n+1);
    int gs=unique(num+1,num+n+1)-num-1;
    for(int i=1;i<=n;i++){
        v[i]=lower_bound(num+1,num+gs+1,v[i])-num;
    }
    dfs(1);
    printf("%d",tr[rt[1]].s);
    return 0;
}
  • 7
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值