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 1Sample Output
5HINT
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 }