*Level Up HDU - 5788【主席树+权值树状数组】

题意:

  公司内有 \(n\) 名员工,除了老板之外,每个人都有一个直接的主管。老板的编号是 \(1\)。每个人都有能力价值 \(A_i\)。一个人 \(i\) 的工资定义为他和其下属(直接和非直接下属)工资的中位数。但公司会选择一个人,使其工资为 \(100000\)。求出所有人工资总和的最大值。
当总数为 \(t\) 时,中位数是第 \(\lceil \frac{t}{2} \rceil\)小的数。
数据范围:
\(1≤n≤100000\)
\(1≤A_i≤100000\)
\(1≤P_i<i\)

分析:

  首先看到求第几小的数,就想到了主席树,而且看数据范围,连离散化都不用。
  接着,通过\(dfs\)序把树拍平,记下每个点的起始位置和所在子树的大小就可以把其所有孩子包括其中。然后,我们考虑,什么时候一个点的子树的中位数会改变。注意当其子树中的一个点变成\(100000\),并且该点原来的值小于中位数。所以,我们可以枚举每个点,看哪些父亲节点会受到影响而改变工资,求出对应的工资的总和。先求出原始的工资和,然后求出最大的改变值。刚才采用递归的过程进行:从根节点向下,利用树状数组区间求和。但是,因为树状数组求得是前缀和,所以可以该点的值 \(x\)\(100001-x\)来代替,就可以把位置变换到前面。对于受影响的父亲节点,其工资会变成原始中位数的下一个数,所以在该位置附上差值,当遍历完子树,要把点清空。

代码:

\(l\) 写成了 \(1\)\(re\) 了几次。

#include <bits/stdc++.h>
#define pb push_back
using namespace std;
typedef long long ll;
const int N=1e5+5;
const int maxn=1e5;
vector<int>pic[N];
ll tree[20*N],bit[N],md;
int lson[20*N],rson[20*N],root[N],cnt;
int sz[N],dfn[N],a[N],mv[N],d[N];
int build(int l,int r)
{
    int t=++cnt;
    tree[t]=0;
    if(l==r)
        return t;
    int mid=(l+r)>>1;
    lson[t]=build(l,mid);
    rson[t]=build(mid+1,r);
    tree[t]=tree[lson[t]]+tree[rson[t]];
    return t;
}
int update(int l,int r,int pos,int rt)
{
    int t=++cnt;
    lson[t]=lson[rt];
    rson[t]=rson[rt];
    if(l==r)
    {
        tree[t]=tree[rt]+1;
        return t;
    }
    int mid=(l+r)>>1;
    if(pos<=mid)
        lson[t]=update(l,mid,pos,lson[rt]);
    else
        rson[t]=update(mid+1,r,pos,rson[rt]);
    tree[t]=tree[lson[t]]+tree[rson[t]];
    return t;
}
int query(int u,int v,int l,int r,int k)
{
    if(l==r)
        return l;
    int mid=(l+r)>>1,num=tree[lson[v]]-tree[lson[u]];
    if(k<=num)
        return query(lson[u],lson[v],l,mid,k);
    else
        return query(rson[u],rson[v],mid+1,r,k-num);
}
void add(int p,int val)
{
    while(p<=maxn)
    {
        bit[p]+=val;
        p+=(p&-p);
    }
}
ll sum(int p)
{
    ll res=0;
    while(p>0)
    {
        res+=bit[p];
        p-=(p&-p);
    }
    return res;
}
void dfs(int v,int &num)
{
    dfn[v]=++num;
    sz[v]=1;
    root[num]=update(1,maxn,a[v],root[num-1]);//遍历的前一个点基础上
    for(int i=0;i<pic[v].size();i++)
    {
        int u=pic[v][i];
        dfs(u,num);
        sz[v]+=sz[u];
    }
}
void solve(int v)//*
{
    add(maxn-mv[v]+1,d[v]);//把在后面的数放在前面
    md=max(md,sum(maxn-a[v]+1));//会影响哪些父亲节点
    for(int i=0;i<pic[v].size();i++)
        solve(pic[v][i]);
    add(maxn-mv[v]+1,-d[v]);//消除
}
void init(int n)
{
    for(int i=1;i<=n;i++)
        pic[i].clear();
    cnt=0,md=0;
    memset(bit,0,sizeof(bit));
}
int main()
{
    int n,u;
    while(scanf("%d",&n)!=EOF)
    {
        init(n);
        for(int i=1;i<=n;i++)
            scanf("%d",&a[i]);
        for(int i=2;i<=n;i++)
        {
            scanf("%d",&u);
            pic[u].pb(i);
        }
        root[0]=build(1,maxn);
        int num=0;
        dfs(1,num);//建主席树
        ll ans=0;
        for(int i=1;i<=n;i++)
        {
            int mid=(sz[i]+1)>>1;//取上界
            if(sz[i]==1)
            {
                mv[i]=a[i];
                d[i]=maxn-a[i];
            }
            else
            {
                mv[i]=query(root[dfn[i]-1],root[dfn[i]+sz[i]-1],1,maxn,mid);//原始工资
                d[i]=query(root[dfn[i]-1],root[dfn[i]+sz[i]-1],1,maxn,mid+1)-mv[i];//更新后的工资
            }
            ans+=mv[i];
        }
        solve(1);
        printf("%lld\n",ans+md);
    }
    return 0;
}

posted @ 2020-03-18 21:34  xzx9  阅读(82)  评论(0编辑  收藏  举报