0x40数据结构进阶(0x42 树状数组)例题3:谜一样的牛

题意

题目链接

【题意】
 有n头奶牛,已知它们的身高为 1~n 且各不相同,但不知道每头奶牛的具体身高。
 现在这n头奶牛站成一列,已知第i头牛前面有Ai头牛比它低,求每头奶牛的身高。

 【输入格式】
 第1行:输入整数n。
 第2..n行:每行输入一个整数Ai,第i行表示第i头牛前面有Ai头牛比它低。
 (注意:因为第1头牛前面没有牛,所以并没有将它列出)

 【输出格式】
 输出包含n行,每行输出一个整数表示牛的身高。
 第i行输出第i头牛的身高。

 【数据范围】
1≤n≤105

【输入样例】
5
 1
 2
 1
 0
【输出样例】
2
 4
 5
 3
 1

题解

方法1

当时并没有看到树状数组QAQ,就直接用平衡树了。

我们一开始设第一个数字为 1 1 1

然后对于第 i i i个数字,我们把前面值域为 [ a [ i ] , i − 1 ] [a[i],i-1] [a[i],i1]的数字全部加 1 1 1,同时自己等于 a [ i ] a[i] a[i],那么就可以完成这个序列的构建,而这个操作我们可以用平衡树随便解决。

时间复杂度 O ( n l o g n ) O(nlogn) O(nlogn)

不足:常数大。

//FHQ treap
#include<cstdio>
#include<cstring>
#include<cstdlib>
#define  N  110000
using  namespace  std;
int  siz[N],vio[N],key[N],son[N][2],cnt,n,root,lazy[N];
int  a[N];
inline  void  update(int  x){siz[x]=siz[son[x][0]]+siz[son[x][1]];}
inline  void  pushdown(int  x){key[x]+=lazy[x];lazy[son[x][0]]+=lazy[x];lazy[son[x][1]]+=lazy[x];lazy[x]=0;}
void  spilt(int  now,int  k,int  &x,int  &y)
{
    if(!now)x=0,y=0;
    else
    {
        pushdown(now);
        if(key[now]<=k)x=now,spilt(son[x][1],k,son[x][1],y);
        else  y=now,spilt(son[y][0],k,x,son[y][0]);
        update(x);update(y);
    }
}
int  merge(int  A,int  B)
{
    if(!A  ||  !B)return  A+B;
    pushdown(A);pushdown(B);
    if(vio[A]<=vio[B])son[A][1]=merge(son[A][1],B);
    else  son[B][0]=merge(A,son[B][0]),A^=B^=A^=B;
    update(A);return  A;
}
void  add(int  x)
{
    cnt++;siz[cnt]=1;vio[cnt]=rand();key[cnt]=x;
    if(!(cnt^1))root=1;
    else
    {
        int  x1,x2;spilt(root,x,x1,x2);
        x1=merge(x1,cnt);root=merge(x1,x2);
    }
}
void  jia(int  l,int  r,int  k)
{
    if(l<=r)
    {
        int  x,y,z;spilt(root,l-1,x,y);spilt(y,r,y,z);
        lazy[y]+=k;
        root=merge(x,y);root=merge(root,z);
    }
}
void  dfs(int  x)
{
    if(!x)return  ;
    pushdown(x);
    dfs(son[x][0]);dfs(son[x][1]);
}
int  main()
{
    srand(999);
    scanf("%d",&n);
    for(int  i=2;i<=n;i++)scanf("%d",&a[i]);
    add(1);
    for(int  i=2;i<=n;i++)
    {
        jia(a[i]+1,i-1,1);
        add(a[i]+1);
    }
    dfs(root);
    for(int  i=1;i<=n;i++)printf("%d\n",key[i]);
    return  0;
}

方法2

后来学习了一下,知道了树状数组or权值线段树的做法。

就是维护一个长度为 n n n 01 01 01序列,第 i i i位表示是否这个数字被用过。

很明显, H n = a n + 1 H_n=a_n+1 Hn=an+1然后删掉 H n H_{n} Hn,即01序列中的 H n H_{n} Hn位变成 0 0 0

那么 H n − 1 = ? H_{n-1}=? Hn1=?,很明显,既然他前面有 a n − 1 a_{n-1} an1个数字小于他,那么 H n − 1 H_{n-1} Hn1就是除 H n H_{n} Hn以外第 a n − 1 + 1 a_{n-1}+1 an1+1大的数字,而这个可以在权值线段树上搞 O ( n l o g n ) O(nlogn) O(nlogn),当然也可以树状数组+二分 O ( n l o g 2 n ) O(nlog^2n) O(nlog2n),或者树状数组加倍增 O ( n l o g n ) O(nlogn) O(nlogn)(其实相当于在线段树上跳,码量小)。

那么对于 H i H_i Hi,就是除 H i + 1 H_{i+1} Hi+1~ H n H_{n} Hn中第 a i + 1 a_{i}+1 ai+1小的数字,每次找到一个 H i H_{i} Hi在01序列删掉这个数字,然后维护一下就行了。

无代码QMQ 。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值