Description
给你一颗有 n 个点的树,其中 1 号点为根节点,每个点都有一个权值
val[i]
你可以从树中选择一些点,注意如果 i 与 j 都被选中且 j 在 i 的子树内,那么必须满足
val[i]>val[j]
请你求出最多能同时选出多少个点
n<=100000
Solution
有两种思路
第一种是先考虑一条链的情况,然后将它拓展到树
一条链的情况非常好做,一个最长上升子序列就好了
最长不下降子序列我们有一种附加数组的方法
那么现在可以用一个multiset来维护这个附加数组
每个儿子之间是不相干的,那么直接用启发式合并将它们合并到一起
考虑插入当前做到的子树的根节点
在multiset中找到第一个大于等于这个值的位置,并将它替换,若它已经是最后一个那就直接加入
最后的答案就是multiset的大小
O(Nlog2N)
第二种是先考虑N^2的DP,再用数据结构优化到N log N
设F[i][j]表示以i为根的子树,最大值为j最多能选多少个点(先离散化)
转移很显然,把j这一维改成前缀最大就是N^2的了
可以利用线段树合并、区间加或者修改完成上面的转移
复杂度
O(NlogN)
第一种代码1K左右,第二种2.5K+
据说第一种还要更快?
Code
第一种方法的代码
#include <cstdio>
#include <cstring>
#include <cmath>
#include <cstdlib>
#include <algorithm>
#include <iostream>
#include <set>
#define fo(i,a,b) for(int i=a;i<=b;i++)
#define fod(i,a,b) for(int i=a;i>=b;i--)
#define N 100005
#define M 2005
using namespace std;
int l,n,fs[N],m,nt[2*N],dt[2*N],rt[N],n1,pr[N];
multiset<int> f[N];
multiset<int>::iterator it;
void hb(int k,int p)
{
for(it=f[p].begin();it!=f[p].end();it++) f[k].insert(*it);
}
void dp(int k)
{
int mx=0,mw=0;
for(int i=fs[k];i;i=nt[i])
{
int p=dt[i];
dp(p);
if(f[rt[p]].size()>mx) mx=f[rt[p]].size(),mw=p;
}
if(!mw) rt[k]=++n1;
else rt[k]=rt[mw];
for(int i=fs[k];i;i=nt[i])
{
int p=dt[i];
if(p!=mw) hb(rt[k],rt[p]);
}
it=f[rt[k]].lower_bound(pr[k]);
int v=f[rt[k]].size();
if(it!=f[rt[k]].end()) f[rt[k]].erase(it);
v=f[rt[k]].size();
f[rt[k]].insert(pr[k]);
}
void link(int x,int y)
{
nt[++m]=fs[x];
dt[fs[x]=m]=y;
}
int main()
{
freopen("tree.in","r",stdin);
freopen("tree.out","w",stdout);
cin>>n;
fo(i,1,n)
{
int x;
scanf("%d%d",&pr[i],&x);
if(x!=0) link(x,i);
}
dp(1);
printf("%d\n",f[rt[1]].size());
}