BZOJ4919:[Lydsy1706月赛]大根堆(set启发式合并)
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
3 0
1 1
2 1
3 1
4 1
5 1
Sample Output
5
Solution
挺巧妙的……
考虑如果只是在序列上做的话,其实这个就是个$LIS$。
现在把他搬到树上其实也差不多,可以每个点开个$multiset$,也就是$nlogn$的$LIS$中的那个单调栈。
每个节点把儿子启发式合并,然后像序列$LIS$一样找到第一个大于等于它的这个数删掉并把它加入。
答案就是根节点$multiset$的$size$
Code
1 #include<iostream> 2 #include<cstdio> 3 #include<set> 4 #define N (200009) 5 using namespace std; 6 7 struct Edge{int to,next;}edge[N]; 8 int n,x,v[N]; 9 int head[N],num_edge; 10 multiset<int>S[N]; 11 multiset<int>::iterator it; 12 13 void add(int u,int v) 14 { 15 edge[++num_edge].to=v; 16 edge[num_edge].next=head[u]; 17 head[u]=num_edge; 18 } 19 20 void DFS(int x) 21 { 22 for (int i=head[x]; i; i=edge[i].next) 23 { 24 int y=edge[i].to; DFS(y); 25 if (S[x].size()<S[y].size()) swap(S[x],S[y]); 26 for (it=S[y].begin(); it!=S[y].end(); ++it) S[x].insert(*it); 27 S[y].clear(); 28 } 29 it=S[x].lower_bound(v[x]); 30 if (it!=S[x].end()) S[x].erase(it); 31 S[x].insert(v[x]); 32 } 33 34 int main() 35 { 36 scanf("%d",&n); 37 for (int i=1; i<=n; ++i) 38 { 39 scanf("%d%d",&v[i],&x); 40 if (x) add(x,i); 41 } 42 DFS(1); 43 printf("%d\n",S[1].size()); 44 }