1. 程式人生 > 其它 >BZOJ 4919 大根堆

BZOJ 4919 大根堆

大根堆

題目描述

給定一棵n個節點的有根樹,編號依次為1到n,其中1號點為根節點。每個點有一個權值v_i。 你需要將這棵樹轉化成一個大根堆。確切地說,你需要選擇儘可能多的節點,滿足大根堆的性質:對於任意兩個點i,j,如果i在樹上是j的祖先,那麼v_i>v_j。 請計算可選的最多的點數,注意這些點不必形成這棵樹的一個連通子樹。

輸入格式

第一行包含一個正整數n(1<=n<=200000),表示節點的個數。 接下來n行,每行兩個整數v_i,p_i(0<=v_i<=10^9,1<=p_i<i,p_1=0),表示每個節點的權值與父親。

輸出格式

輸出一行一個正整數,即最多的點數。

樣例

樣例輸入

6
3 0
1 1
2 1
3 1
4 1
5 1

樣例輸出

5
#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
#define ll long long
inline int in(){
    int x = 0;
    bool f = 1;
    char c = getchar();
    while(c > '9' || c < '0'){
        
if(c == '-') f = 0; c = getchar(); } while(c <= '9' && c >= '0'){ x = (x << 3) + (x << 1) + (c ^ 48); c = getchar(); } if(f) return x; else return -x; } const int N = 2e5; #define ls(x) t[x].ls #define rs(x) t[x].rs struct node{
int ls,rs,sum; }t[N << 2];//每個節點獨立存在不需要pushup int to[N],head[N],nxt[N],cnt; int lsh[N],f[N],v[N]; int M,tot; inline void add(int u,int v){ to[++cnt] = v; nxt[cnt] = head[u]; head[u] = cnt; } void update(int &rt,int l,int r,int L,int R){ if(!rt) rt = ++tot; if(L <= l && r <= R){ t[rt].sum++; return; } int mid = (l + r) >> 1; if(L <= mid) update(ls(rt),l,mid,L,R); if(mid < R) update(rs(rt),mid+1,r,L,R); } int merge(int ra,int rb,int l,int r){ if(!ra || !rb) return ra + rb; t[ra].sum += t[rb].sum; int mid = (l + r) >> 1; ls(ra) = merge(ls(ra),ls(rb),l,mid); rs(ra) = merge(rs(ra),rs(rb),mid+1,r); return ra; } int query(int rt,int l,int r,int val){ if(!rt) return 0; int mid = (l + r) >> 1; int res = t[rt].sum; if(val <= mid) res += query(ls(rt),l,mid,val); else res += query(rs(rt),mid+1,r,val); return res; } void dfs(int x){ for(int i = head[x];i;i = nxt[i]){ int y = to[i]; dfs(y); f[x] = merge(f[x],f[y],1,M); } int ans1 = query(f[x],1,M,v[x]-1) + 1;//選這個節點,所以去找小於v[u]-1的節點數 int ans2 = query(f[x],1,M,v[x]);//不選這個節點 if(ans1 <= ans2) return;//選了還不如不選,那麼就算選了也不是最優解 int l = v[x],r = M,pos = v[x]; //如果這個節點要選,可能子結點中比自己大的節點的可選節點數大於自己 //由於這個節點是+1得到的,那麼就要去找到比自己大的節點中可選節點數小於自己的最大的節點 //也就是要找一個最大的區間,使得這個區間中每個點的可選節點數都要小於自己 //然後把這個區間的值+1 while(l <= r){//二分找到最大的區間右端點 int mid = (l + r) >> 1; if(query(f[x],1,M,mid) < ans1){ l = mid + 1; pos = mid; }else r = mid - 1; } update(f[x],1,M,v[x],pos); } int main(){ //freopen("in.txt","r",stdin); //freopen("out.txt","w",stdout); int n = in(); for(int i = 1;i <= n;++i){ lsh[i] = v[i] = in(); int x = in(); add(x,i); } sort(lsh+1,lsh+n+1); M = unique(lsh+1,lsh+n+1) - lsh - 1; for(int i = 1;i <= n;++i) v[i] = lower_bound(lsh+1,lsh+M+1,v[i]) - lsh; dfs(1); printf("%d",query(f[1],1,M,M));//大根堆,搜最大的就好了 return 0; }
AC 程式碼