1. 程式人生 > >[BZOJ4919][Lydsy1706月賽]大根堆

[BZOJ4919][Lydsy1706月賽]大根堆

names AC tput 多少 如何 LG bsp ems 最大值

4919: [Lydsy1706月賽]大根堆

Time Limit: 10 Sec Memory Limit: 256 MB
Submit: 591 Solved: 256
[Submit][Status][Discuss]

Description

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

Input

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

Output

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

Sample Input

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

Sample Output

5

HINT

Source

本OJ付費獲得

[Submit][Status][Discuss]

容易想出f[i][j]表示節點i的子樹,最大值為j,最多能選幾個點。

DP方程顯然,可以線段樹區間修改優化,不同子樹間要進行帶標記線段樹啟發式合並,這就很麻煩了。

重新考慮這個問題,容易發現如果是一條鏈的話實際上就是在問LIS,思考如何搬到樹上。

LIS的經典二分做法是:f[i]表示到當前為止長度為i的LIS末尾最大為多少,每次二分更新一個位置,而最大的非零f位置就是到當前為止的LIS長度。

這個東西也可以用set維護,當前的size()就是LIS長度,這樣就可以搬到樹上了,兩個子樹啟發式合並即可,十分巧妙。

 1 #include<set>
 2 #include<cstdio>
 3 #include<algorithm>
 4
#define rep(i,l,r) for (int i=l; i<=r; i++) 5 using namespace std; 6 7 const int N=200010; 8 int n,cnt,x,h[N],a[N],to[N<<1],nxt[N<<1]; 9 multiset<int>f[N]; 10 11 void add(int u,int v){ to[++cnt]=v; nxt[cnt]=h[u]; h[u]=cnt; } 12 void dfs(int x,int fa){ 13 for (int i=h[x],k; i; i=nxt[i]) if ((k=to[i])!=fa){ 14 dfs(k,x); if (f[k].size()>f[x].size()) swap(f[x],f[k]); 15 for (set<int>::iterator j=f[k].begin(); j!=f[k].end(); j++) f[x].insert(*j); 16 f[k].clear(); 17 } 18 if (f[x].size()>0 && f[x].lower_bound(a[x])!=f[x].end()) f[x].erase(f[x].lower_bound(a[x])); 19 f[x].insert(a[x]); 20 } 21 22 int main(){ 23 freopen("bzoj4919.in","r",stdin); 24 freopen("bzoj4919.out","w",stdout); 25 scanf("%d%d%d",&n,&a[1],&x); 26 rep(i,2,n) scanf("%d%d",&a[i],&x),add(x,i),add(i,x); 27 dfs(1,0); printf("%d\n",(int)f[1].size()); 28 return 0; 29 }

[BZOJ4919][Lydsy1706月賽]大根堆