[bzoj4919][splay]大根堆

版权声明:他无力阻止你的转载 https://blog.csdn.net/qq_36993218/article/details/79965512

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()); 
}
阅读更多
想对作者说点什么?

博主推荐

换一批

没有更多推荐了,返回首页