树状数组 - 谜一样的牛
题目:
有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
题解:
给 定 的 第 2 到 n 头 牛 的 信 息 , 首 先 可 以 确 定 的 就 是 最 后 一 头 的 身 高 。 假 设 第 n 头 牛 的 信 息 是 a n , 即 前 n − 1 头 牛 当 中 , 比 第 n 头 牛 高 的 共 有 a n 头 , 即 第 n 头 牛 是 第 a n + 1 高 的 , n 头 牛 的 身 高 分 别 是 前 n 大 的 自 然 数 , 第 n 头 牛 的 身 高 就 是 就 是 第 a n + 1 大 的 自 然 数 。 以 此 类 推 , 第 n − 1 头 牛 的 身 高 就 是 剩 下 的 n − 1 个 自 然 数 当 中 第 a n − 1 + 1 大 的 自 然 数 。 可 见 , 我 们 每 次 都 需 要 看 还 剩 下 多 少 自 然 数 没 有 被 选 过 , 在 剩 下 的 这 些 数 当 中 再 选 择 第 k 大 的 。 因 此 , 可 以 用 树 状 数 组 来 统 计 剩 下 的 自 然 数 的 个 数 。 把 每 个 自 然 数 初 始 化 标 记 为 1 , 每 选 择 一 个 自 然 数 , 就 将 其 标 记 为 0 , 求 [ 1 , x ] 的 前 缀 和 即 求 前 x 个 自 然 数 中 剩 下 的 自 然 数 的 个 数 。 查 找 剩 下 的 自 然 数 中 第 k 大 的 自 然 数 可 以 二 分 , 找 到 第 一 个 前 缀 和 等 于 k 的 位 置 , 这 个 下 标 就 是 剩 下 的 第 k 大 的 自 然 数 。 给定的第2到n头牛的信息,首先可以确定的就是最后一头的身高。\\假设第n头牛的信息是a_n,即前n-1头牛当中,比第n头牛高的共有a_n头,即第n头牛是第a_n+1高的,\\n头牛的身高分别是前n大的自然数,第n头牛的身高就是就是第a_n+1大的自然数。\\\ \\以此类推,第n-1头牛的身高就是剩下的n-1个自然数当中第a_{n-1}+1大的自然数。\\\ \\可见,我们每次都需要看还剩下多少自然数没有被选过,在剩下的这些数当中再选择第k大的。\\因此,可以用树状数组来统计剩下的自然数的个数。\\把每个自然数初始化标记为1,每选择一个自然数,就将其标记为0,\\求[1,x]的前缀和即求前x个自然数中剩下的自然数的个数。\\\ \\查找剩下的自然数中第k大的自然数可以二分,找到第一个前缀和等于k的位置,这个下标就是剩下的第k大的自然数。 给定的第2到n头牛的信息,首先可以确定的就是最后一头的身高。假设第n头牛的信息是an,即前n−1头牛当中,比第n头牛高的共有an头,即第n头牛是第an+1高的,n头牛的身高分别是前n大的自然数,第n头牛的身高就是就是第an+1大的自然数。 以此类推,第n−1头牛的身高就是剩下的n−1个自然数当中第an−1+1大的自然数。 可见,我们每次都需要看还剩下多少自然数没有被选过,在剩下的这些数当中再选择第k大的。因此,可以用树状数组来统计剩下的自然数的个数。把每个自然数初始化标记为1,每选择一个自然数,就将其标记为0,求[1,x]的前缀和即求前x个自然数中剩下的自然数的个数。 查找剩下的自然数中第k大的自然数可以二分,找到第一个前缀和等于k的位置,这个下标就是剩下的第k大的自然数。
代码:
#include <cstdio>
using namespace std;
const int N=1e5+10;
int n,a[N],tr[N],ans[N];
int lowbit(int x)
{
return x&-x;
}
void add(int x,int c)
{
for(int i=x;i<=n;i+=lowbit(i)) tr[i]+=c;
}
int sum(int x)
{
int res=0;
for(int i=x;i;i-=lowbit(i)) res+=tr[i];
return res;
}
int main()
{
scanf("%d",&n);
for(int i=2;i<=n;i++) scanf("%d",&a[i]);
for(int i=1;i<=n;i++) tr[i]=lowbit(i); // 等价于add(i,1)
for(int i=n;i;i--)
{
int k=a[i]+1; ///第k小的数即身高
int l=1,r=n;
while(l<r)
{
int mid=l+r>>1;
if(sum(mid)>=k) r=mid;
else l=mid+1;
}
ans[i]=r;
add(r,-1);
}
for(int i=1;i<=n;i++) printf("%d\n",ans[i]);
return 0;
}