题意:
思路:
我们用h[]
存推理出所有牛的身高,a[i]
表示当前第i
头牛左边有多少牛比较低,c[]
表示树状数组。
我们发现,如果说 第i
头牛的左边有a[i]
头牛比它矮,那么它的身高h[i]
要满足两个条件:
-
①在数值
1~n
中排行第a[i] + 1
小。 -
②没有在
h[i+1],h[i+2],…,h[n]
中 出现过。 -
(由于我们采用倒序遍历,
h[i+1]~h[n]
这些第i
头牛右边所有牛的身高已确定,当然不能选了)
大体流程:
我们倒序遍历每个数a[i]
:
-
①每次从剩余的数中找到第
k=a[i]+1
小的数(这是上方我们找到的规律)。 -
②删除这个数。
分析:
本来执行上述两个步骤就已经可以求出答案了,但是观察数据范围:n<=1e5
,步骤①中由于遍历每一个数的时候都要从剩下的数中查找目标值,需要O(n^2)
级别的复杂度,我们显然要加以优化,考虑利用树状数组加二分来优化。
先让a[1]~a[n]
都为1
,表示 这个身高我们还可以用一次,使用树状数组c
在O(logn)
的时间复杂度内维护a[1]~a[n]
的前缀和(树状数组都是用来维护前缀和的),如果要删除某个数直接在这个位置上减1即可(单点修改,也是O(logn),add(x, -1)
)
在本题中,ask(x)
表示区间1~x
中 剩余了多少个数,如果我们想在剩余的ask(x)
个数中找到第k
小的数,应该要寻找的是最小的x
,使得ask(x)=k
。
即步骤②等价于寻找一个最小的x
,使得ask(x)=k
。(ask(x)<k
表示1~x
中不足k
个数,肯定是不行的)
由于ask(x)
显然是单调的,因此上述的寻找过程我们可以用二分来求。
总结一下:也就是说,我们要实时维护一个01
序列,支持查询第k
个1
的位置(二分查找左边界),以及修改序列中的一个数值。
(01
序列不断在变化,因此二分查找的左右边界始终包含了整个1~n
的区域)
时间复杂度 :
O(n(logn^2))
(树状数组和二分常数都非常小,可能比平衡树还要快)
代码:(代码细节都体现在了上述的题解中)
#include<bits/stdc++.h>
using namespace std;
const int N = 1e5+10;
int h[N], a[N], c[N];
int n;
inline int lowbit(int x) {return x&(-x);}
int ask(int x)
{
int ans = 0;
while(x) {ans+=c[x], x-=lowbit(x);}
return ans;
}
int add(int x, int y)
{
for(; x<=n; x+=lowbit(x)) c[x]+=y;
}
int main()
{
cin>>n;
add(1, 1);
for(int i=2;i<=n;++i)
{
scanf("%d", &a[i]);
add(i, 1);
}
for(int i=n; i>=1; --i)
{
int k = a[i]+1;
int l = 1, r = n;//01序列不断在变化,因此二分查找的左右边界始终包含了整个1~n的区域
while(l<r)
{
int mid = l+r>>1;
if(ask(mid)>=k) r = mid;
else l = mid + 1;
}
h[i] = l;
add(l, -1);
}
for(int i=1;i<=n;++i) printf("%d\n", h[i]);
return 0;
}