[BZOJ4919][Lydsy1706月赛]大根堆

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

[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
",(int)f[1].size());
28     return 0;
29 }
原文地址:https://www.cnblogs.com/HocRiser/p/8970601.html