Toc
  1. BZOJ4756 [Usaco2017 Jan] Promotion Counting
    1. Description
    2. Input
    3. Output
    4. Sample Input
    5. Sample Output
    6. Solution
Toc
0 results found
sher-wu
BZOJ4756

BZOJ4756 [Usaco2017 Jan] Promotion Counting


Description

n只奶牛构成了一个树形的公司,每个奶牛有一个能力值pi,1号奶牛为树根。
问对于每个奶牛来说,它的子树中有几个能力值比它大的。

Input

n,表示有几只奶牛 n<=100000
接下来n行为1-n号奶牛的能力值pi
接下来n-1行为2-n号奶牛的经理(树中的父亲)

Output

共n行,每行输出奶牛i的下属中有几个能力值比i大

Sample Input

5
804289384
846930887
681692778
714636916
957747794
1
1
2
3

Sample Output

2
0
1
0
0

Solution

如果每个节点都给一个线段树,然后暴力的在父节点将两个线段树合并,那么空间肯定是不够的。因而首先要在空间上进行优化。这里引入了树上的线段树合并。
"树上"还是原来的,DFS的顺序从下往上维护。
"线段树合并"的步骤如下(这里的线段树实际上是一个链):
对于两颗树的节点u和v
  ①如果u为空,返回v
  ②如果v为空,返回u
  ③否则,新建节点t,整合u和v的信息,然后递归合并u和v的左右子树

inline int merge(int u,int v)
{
    if(!u) return v;
    if(!v) return u;
    int t = ++cnt;
    sum[t]=sum[u]+sum[v];
    les[t]=merge(les[u],les[v]);
    ris[t]=merge(ris[u],ris[v]);
    return t;
}

思考可知,每有一个位置权值同样存在,就要\(O(logn)\)的复杂度。那么合并的复杂度取决于两棵线段树重合部分的大小。
由于权值线段树中被更新的位置通常很均匀分布,所以合并的两棵线段树通常具有很小的相似性。
线段树合并的空间复杂度和时间复杂度都是\(O(nlogn)\)的。
(点大概要开\(2nlogn\)个,测了一下\(n=131072\)‬的时候\(32n\)\(n=524288\)的时候\(36n\)‬,那么\(2e7\)个应该是肯定够的)

后来看了网上的博客,发现其实这题有更简单的做法。。。
按DFS序把所有点放入并取出一个栈,放入时计算小于\(a_i\)的数量并把\(a_i\)放入线段树中,取出时再计算一次小于\(a_i\)的数量,那么两者之差就是答案。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 2e5+20;
const int maxm = 2e7+20;
int n,id[maxn],a[maxn],b[maxn],fa[maxn],ans[maxn];
int cnt,pid[maxn],sum[maxm],les[maxm],ris[maxm];
vector<int> son[maxn];
inline ll read(){
    ll x=0,f=1;
    char ch=getchar();
    while(ch<'0'||ch>'9'){
        if(ch=='-') f=-1;
        ch=getchar();
    }
    while(ch>='0'&&ch<='9'){
        x=(x<<1)+(x<<3)+(ch^48);
        ch=getchar();
    }
    return x*f;
}

inline void readin()
{
    n=read();
    for(int i=1;i<=n;++i) a[i]=read(),b[i]=a[i];
    sort(b+1,b+1+n);//题目给定了能力值互异,故不用去重 
    for(int i=1;i<=n;++i) id[i]=lower_bound(b+1,b+1+n,a[i])-b;
    for(int i=2;i<=n;++i) fa[i]=read(),son[fa[i]].push_back(i);
}

inline int query(int qid,int l,int r,int pos)
{
    if(!qid) return 0;
    if(l>pos) return sum[qid];
    int m=(l+r)>>1;
    if(m>pos) return query(ris[qid],m+1,r,pos)+query(les[qid],l,m,pos);
    return query(ris[qid],m+1,r,pos);
}

inline int merge(int u,int v)//the core
{
    if(!u) return v;//某个没有,就直接用另一个,儿子信息可以直接用,不需要更新
    if(!v) return u;
    int t = ++cnt;//都有,就另建一个点记录合并后的信息
    sum[t]=sum[u]+sum[v];
    les[t]=merge(les[u],les[v]);//并且维护新节点的儿子信息
    ris[t]=merge(ris[u],ris[v]);
    return t;
}

inline void modify(int& qid,int l,int r,int pos)//add the current point
{
    if(!qid) qid=++cnt;
    ++sum[qid];
    if(l==r) return;
    int m=(l+r)>>1;
    if(m>=pos) modify(les[qid],l,m,pos);
    else modify(ris[qid],m+1,r,pos);
}

inline int dfs(int now)
{
    for(int i=0;i<son[now].size();++i)
    {
        dfs(son[now][i]);
        pid[now]=merge(pid[now],pid[son[now][i]]);
    }
    ans[now]=query(pid[now],1,n,id[now]);
    modify(pid[now],1,n,id[now]);//add the current point
}

int main()
{
    readin();
    dfs(1);
    for(int i=1;i<=n;++i) printf("%d\n",ans[i]);
    return 0;
}
打赏
支付宝
微信
本文作者:sher-wu
版权声明:本文首发于sher-wu的博客,转载请注明出处!