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\)。
然后对于第\(i\)个数字,我们把前面值域为\([a[i],i-1]\)的数字全部加\(1\),同时自己等于\(a[i]\),那么就可以完成这个序列的构建,而这个操作我们可以用平衡树随便解决。
时间复杂度\(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\)的\(01\)序列,第\(i\)位表示是否这个数字被用过。
很明显,\(H_n=a_n+1\)然后删掉\(H_{n}\),即01序列中的\(H_{n}\)位变成\(0\)。
那么\(H_{n-1}=?\),很明显,既然他前面有\(a_{n-1}\)个数字小于他,那么\(H_{n-1}\)就是除\(H_{n}\)以外第\(a_{n-1}+1\)大的数字,而这个可以在权值线段树上搞\(O(nlogn)\),当然也可以树状数组+二分\(O(nlog^2n)\),或者树状数组加倍增\(O(nlogn)\)(其实相当于在线段树上跳,码量小)。
那么对于\(H_i\),就是除\(H_{i+1}\)~\(H_{n}\)中第\(a_{i}+1\)小的数字,每次找到一个\(H_{i}\)在01序列删掉这个数字,然后维护一下就行了。
无代码QMQ 。