Description
给定一棵n个节点的有根树,编号依次为1到n,其中1号点为根节点。每个点有一个权值v_i。
你需要将这棵树转化成一个大根堆。确切地说,你需要选择尽可能多的节点,满足大根堆的性质:对于任意两个点i,j,如果i在树上是j的祖先,那么v_i>v_j。
请计算可选的最多的点数,注意这些点不必形成这棵树的一个连通子树。
Sample Input
6
3 0
1 1
2 1
3 1
4 1
5 1
Sample Output
5
一开始一看,觉得这题是个树上LIS。
然后就想残了,打了个主席树。。。
发现诸多问题,于是跑去膜题解。
对于一棵子树,考虑用set维护。
我们肯定希望集合中最后一个元素尽量小。
每次当前节点合到属于x的集合中去时,我们作如下操作:
寻找当前x值的lower_bound,将其删除,然后将x插到set中去。
这样对答案是没有影响的,考虑小于当前x的值,显然没有影响,再考虑,后面的元素。
如果当前值的lower_bound为集合中最后一个元素,显然可用这个更小的值替换。
如果当前值的lower_bound不为集合中最后一个元素,也就是说x暂时不能对答案构成更新,直到后面不断利用x的值使得set结尾的值更小,这时x才能做出贡献,显然这样是合法的,如:
当前数列1,2,3,9,10
这时x进入,值为4,数列变为:1,2,3,4,10
这时x的父亲出现一个5,数列变为:1,2,3,4,5
此时数组结尾对比原来要小,并且此时可用x,x的父亲,权值为1,2,3的节点构成一棵合法树,故x的替换不造成影响。
注意还有一个细节,将x的子节点y的set合到x上时,建议使用启发式合并。
#include <set>
#include <cstdio>
#include <cstring>
#include <algorithm>
#define It multiset<int>::iterator
using namespace std;
int _max(int x, int y) {return x > y ? x : y;}
int read() {
int s = 0, f = 1; char ch = getchar();
while(ch < '0' || ch > '9') {if(ch == '-') f = -1; ch = getchar();}
while(ch >= '0' && ch <= '9') s = s * 10 + ch - '0', ch = getchar();
return s * f;
}
struct edge {
int x, y, next;
} e[210000]; int len, last[210000];
int a[210000];
multiset<int> q[210000];
void ins(int x, int y) {
e[++len].x = x; e[len].y = y;
e[len].next = last[x]; last[x] = len;
}
void Merge(int x, int y) {
if(q[x].size() < q[y].size()) swap(q[x], q[y]);
for(It it = q[y].begin(); it != q[y].end(); ++it) q[x].insert(*it);
q[y].clear();
}
void dfs(int x) {
int hh = 1;
for(int k = last[x]; k; k = e[k].next) {
int y = e[k].y;
dfs(y);
Merge(x, y);
} It it = q[x].lower_bound(a[x]);
if(it != q[x].end()) q[x].erase(it);
q[x].insert(a[x]);
}
int main() {
int n = read();
for(int i = 1; i <= n; i++) {
a[i] = read(); int f = read();
if(f) ins(f, i);
} dfs(1);
printf("%d\n", q[1].size());
return 0;
}