题目链接
AcWing 244. 谜一样的牛
easy
题目描述
有 n n n 头奶牛,已知它们的身高为 1 ∼ n 1∼n 1∼n 且各不相同,但不知道每头奶牛的具体身高。
现在这 n n n 头奶牛站成一列,已知第 i i i 头牛前面有 A i Ai Ai 头牛比它低,求每头奶牛的身高。
输入格式
-
第 1 1 1 行:输入整数 n n n
-
第 2.. n 2..n 2..n 行:每行输入一个整数 A i Ai Ai,第 i i i 行表示第 i i i 头牛前面有 A i Ai Ai 头牛比它低。(注意:因为第 1 1 1 头牛前面没有牛,所以并没有将它列出)
输出格式
-
输出包含 n n n 行,每行输出一个整数表示牛的身高。
-
第 i i i 行输出第 i i i 头牛的身高。
数据范围
- 1 ≤ n ≤ 1 0 5 1≤n≤10^5 1≤n≤105
输入样例:
5
1
2
1
0
输出样例:
2
4
5
3
1
解法一:树状数组 + 二分
有 n n n 头牛,并且它们的身高分别是 1 ∼ n 1 \sim n 1∼n,所以说它们的身高是互不重复的。
由于从中间开始考虑,难以解决。所以我们可以选择从边界开始考虑,即从最后一头牛开始考虑。假设最后一头牛之前有 A n A_n An 头牛比它矮,那么就可以确定最后一头牛的高度肯定是 A n + 1 A_n + 1 An+1。也就是区间 [ 1 , n ] [1,n] [1,n] 中第 A n + 1 A_n + 1 An+1 小的数。
继续考虑倒数第二头牛,假设倒数第二头牛前面有 A n − 1 A_{n - 1} An−1 头牛比它矮,那么倒数第二头牛的高度就应该是剩余区间(区间 [ l , r ] [l,r] [l,r] 去掉了位置 A n + 1 A_n + 1 An+1)中 第 A n − 1 + 1 A_{n-1} + 1 An−1+1 小的数。
在实现上我们可以用一个树状数组 c c c 来维护一个初始化全为 1 1 1 的数组 b b b 的前缀和。
对于最后一头牛,我们要先求出它在区间中的位置 A n + 1 A_n + 1 An+1,这个位置也就是它的高度。由于前缀和数组是有序的,所以我们可以利用 二分 的方式快速求出。求出位置 p o s pos pos 之后,记录答案。记录之后,将 c [ p o s ] c[pos] c[pos] 的值加上 − 1 -1 −1,并维护它的前缀和,表示这个数已经被选了,剩下的 n − 1 n - 1 n−1头牛只能从 剩下的 n − 1 n - 1 n−1 个数中选择高度了。
对于每一头牛我们都重复这样的操作,直到结束,最后就能求出每一头牛的高度。
时间复杂度: O ( n × l o g 2 n ) O(n \times log^2n) O(n×log2n)
C++代码:
#include<iostream>
#include<vector>
using namespace std;
const int N = 1e5+10;
int c[N] , a[N];
int n;
inline int lowbit(int x){
return x & -x;
}
inline int query(int x){
int ans = 0;
for(;x;x -= lowbit(x)) ans += c[x];
return ans;
}
void add(int x,int k){
for(;x <= n;x += lowbit(x)) c[x] += k;
}
int main(){
cin>>n;
for(int i = 2;i <= n;++i) scanf("%d",&a[i]);
//等价于每次都 add(i,1) , 即构建一个初始化全为 1 的数组 的树状数组c
for(int i = 1;i <= n;++i) c[i] = lowbit(i);
vector<int> ans;
for(int i = n;i >= 1;--i){
int l = 1 , r = n;
while(l < r){
int mid = (l + r) >> 1;
int t = query(mid);
//求当前的第 i 头牛在剩余区间中第 a[i] + 1 小的位置 , 也就是它对应的高度
if(t >= a[i] + 1) r = mid;
else l = mid + 1;
}
//l 就是当前牛的高度
ans.push_back(l);
//表示这个高度已经被占了 , 剩下的牛只能从剩下的数中选择高度
add(l , -1);
}
for(int i = n - 1;i >= 0;--i) cout<<ans[i]<<'\n';
return 0;
}
解法二:树状数组 + 倍增
我们还是用 c c c 维护初始化全为 1 1 1 数组的前缀和。
对于每一头牛(倒序开始考虑):
- 初始化两个变量 p o s = 0 , s u m = 0 pos = 0 , sum = 0 pos=0,sum=0;
- 对于 s s s ,倒序枚举这样的一个区间 [ 0 , ⌊ l o g ( n ) ⌋ ] [0 , \lfloor log(n) \rfloor] [0,⌊log(n)⌋];
- 对于每一个 s s s,如果 p o s + 2 s ≤ n pos + 2^s \leq n pos+2s≤n 并且 s u m + c [ p o s + 2 s ] < k sum + c[pos + 2^s] < k sum+c[pos+2s]<k,则让 p o s = p o s + 2 s pos = pos + 2^s pos=pos+2s , s u m = s u m + c [ p o s + 2 n ] sum = sum + c[pos + 2^n] sum=sum+c[pos+2n];
- 最后 p o s + 1 pos + 1 pos+1就是我们要求的该头牛的高度。记录答案之后,调用 a d d ( p o s + 1 , − 1 ) add(pos + 1 , -1) add(pos+1,−1) 表示该高度已经被选了。
时间复杂度: O ( n × l o g n ) O(n \times logn) O(n×logn)
C++代码:
#include <iostream>
#include <cmath>
#include<vector>
using namespace std;
const int N = 1e5+10;
int n;
int a[N], c[N];
int lowbit(int x)
{
return x & -x;
}
int query(int x)
{
int ans = 0;
for(;x;x -= lowbit(x)) ans += c[x];
return ans;
}
void add(int x,int k)
{
for(;x <= n;x += lowbit(x)) c[x] += k;
}
int main()
{
cin>>n;
for (int i = 2;i <= n;++i) scanf("%d", &a[i]);
for (int i = 1;i <= n;++i) c[i] = lowbit(i);
int k = log2(n);
vector<int> ans;
for (int i = n;i >= 1;--i) {
int pos = 0, sum = 0;
for (int s = k;s >= 0;--s) {
if (pos + (1 << s) <= n && sum + c[pos + (1 << s)] < a[i] + 1) {
sum += c[pos + (1 << s)];
pos += (1 << s);
}
}
ans.push_back(pos + 1);
add(pos + 1 , -1);
}
for (int i = n - 1;i >= 0;--i) {
printf("%d\n",ans[i]);
}
return 0;
}