题解:P10497 【重题】

· · 题解

呃啊,怎么改题了。

于是我,又过来了。

因为改题后也很简单。

题意是这样的:

n 个数组成的数列,已知它们每个数前面有多少个比它小的数。

求每个数的排名。

设已知的大小数列为 a,排名数列为 rk

从后向前考虑,因为 rk_n 就等于 a_n+1

这个很好想。

因为前 n-1 个数加它本身就是整个数列。

接着,推不动了。

即使知道了 rk_n 也无法推算 rk_{n-1}

于是,辅助数组,启动。

可以设置一个 bool 型的辅助数组 f

计算出 $rk_n$ 可以更新 $f$ 数组。 然后,就在剩下的 $n-1$ 个 $1$ 中选择。 这 $n-1$ 个 $1$ 代表前 $n-1$ 个没选的。 第 $n-1$ 个数是前 $n-1$ 个数的第 $a_{n-1}+1$ 小。 所以,在现存 $f$ 数组上,选择正数第 $k$ 个 $1$ 的那个位子(下标),就是 $rk_{n-1}$。 然后,修改 $f$ 数组,依此类推。 梳理一下,我们需要(对 $f$ 数组)干什么。 第一,单点修改。 第二,查询前缀和为 $k$ 的位置。 仅此而已。 单点加前缀,可以启动树状数组。 查询位置,启动二分。 时间复杂度 $O(n\log^2n)$。 不够优秀。 因为树状数组是倍增思想的产物,所以果断选择用倍增替换二分。 每次倍增,我们需要求区间 $[2^a+2^b\dots2^n+1,2^a+2^b\dots2^n+2^m]$ 的值。 等一下,这个值不是树状数组 $c_{2^a+2^b\dots2^n+2^m}$ 的值吗? 赚大了。 第 $k$ 小,时复降低到 $O(\log n)$ 了(倍增的时间)。 时间复杂度 $O(n\log n)$。 代码。 ```cpp #include<bits/stdc++.h> using namespace std; int n; int a[100010]; int c[100010]; inline int lowbit(int x){ return x&-x; } inline void update(int x,int k){ while(x<=n) c[x]+=k,x+=lowbit(x); } inline int get_kth(int k){ int ans=0,sum=0; for(int i=30;i>=0;i--){ if(ans+(1<<i)>n) continue; if(sum+c[ans+(1<<i)]<k) ans+=(1<<i),sum+=c[ans]; } return ans+1; } int ans[100010]; int main() { ios::sync_with_stdio(0); cin>>n; update(1,1); for(int i=2;i<=n;i++) cin>>a[i],update(i,1); for(int i=n;i>=1;i--){ int p=get_kth(a[i]+1); update(p,-1); ans[i]=p; } for(int i=1;i<=n;i++) cout<<ans[i]<<"\n"; return 0; } ```