4919: [Lydsy1706月赛]大根堆
Time Limit: 10 Sec Memory Limit: 256 MB
Submit: 500 Solved: 225
[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
sol:
一开始的想法是这样的,f[i][j]表示i的子树中选出的数的权值<=j的情况下能选出的最多的点数。那么f[u]就是把f[v]全部加起来之后,把f[u][a[u]-1]+1拿去更新后面的数。也就是说要取个max,然后我们用线段树合并来优化这个过程即可。但是带标记的线段树合并写起来挺麻烦的。去看了一下网上的题解,网上是说这是个变种的lis,我一想这个tm好像很对啊。f[i][j]表示当前i子树选了j个点的最大权值的最小值。然后就可以用multiset启发式合并了。
#include<cstdio>
#include<algorithm>
#include<string>
#include<cstring>
#include<cstdlib>
#include<cmath>
#include<set>
#include<iostream>
#define it multiset<int>
using namespace std;
typedef long long ll;
typedef double db;
int n,m;
inline int read()
{
char c;
int res,flag=0;
while((c=getchar())>'9'||c<'0') if(c=='-')flag=1;
res=c-'0';
while((c=getchar())>='0'&&c<='9') res=(res<<3)+(res<<1)+c-'0';
return flag?-res:res;
}
const int N=410000;
int tot,fir[N],go[N],nex[N];
inline void add(int x,int y)
{
nex[++tot]=fir[x];fir[x]=tot;go[tot]=y;
nex[++tot]=fir[y];fir[y]=tot;go[tot]=x;
}
it q[N];
int a[N];
inline void dfs(int u,int f)
{
int e,v;
it::iterator i;
for(e=fir[u];v=go[e],e;e=nex[e])
if(v^f)
{
dfs(v,u);
if(q[u].size()<q[v].size()) q[u].swap(q[v]);
for(i=q[v].begin();i!=q[v].end();++i) q[u].insert(*i);
}
i=q[u].lower_bound(a[u]);
if(i!=q[u].end()) q[u].erase(i);
q[u].insert(a[u]);
}
int main()
{
// freopen("4919.in","r",stdin);
// freopen("4919.out","w",stdout);
n=read();
for(int i=1;i<=n;++i)
{
a[i]=read();
add(read(),i);
}
dfs(1,0);
printf("%d\n",q[1].size());
}