题解:UVA11525 Permutation

· · 题解

UVA11525 题解

题面

原题传送门

前置知识

正向康托展开

题意很简单,就是求长度为 n 的数组 a 在全部 n 的排列中按字典序排序的排名。

先那一组数据来手玩:

4 2 3 5 1 的排名。

所以,该排列的排名为 72+6+2+1+1=82 名,记得最后要加 1,因为我们算的是该排列前面有多少个排列。

形式化的,记 s_i\gets\begin{aligned}\sum_{j=i+1}^n [a_j<a_i]\end{aligned},则 a 的排名就为 \begin{aligned}\sum_{i=1}^n s_i\times(n-i)!\end{aligned}

如果暴力求这个数组的话是 O(n^2),会超时,所以考虑用树状数组优化,就可以达到 O(n\log n),原理和树状数组求逆序对一样。

思路

把本题的题面对比下正向康托展开发现式子一模一样,所以也就知道这题中的 S_i 就是 \begin{aligned}\sum_{j=i+1}^n [a_j<a_i]\end{aligned},所以问题就转化成了已知每个数后面有几个小于自己的数,求这个序列。

这个很好办,用树状数组加二分,我们可以从前往后一个一个确定 a

在逐步确定 $a$ 过程中,对于每个 $i\in\{1,2,\cdots,n\}$,比 $i$ 小并且未被确定的数的个数满足单调性,于是就可以二分求 $a_i$,然后标上 $a_i$ 已被标记(在树状数组中把 $a_i$ 位置减一)。 时间复杂度为 $O(n\log^2 n)$,用线段树上二分可以优化到 $O(n\log n)$,但是笔者太懒(菜)了,只打了树状数组(反正能过就行)。 ### 实现细节 一定要记得每一行的最后不能打空格!!! ### 代码 ```cpp #include<iostream> #include<cstdio> #include<algorithm> #include<cstring> #include<cmath> #define ll long long using namespace std; const int MN=5e4+5; ll n,t[MN],a[MN]; void write(ll n){if(n<0){putchar('-');write(-n);return;}if(n>9)write(n/10);putchar(n%10+'0');} ll read(){ll x=0,f=1;char ch=getchar();while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}return x*f;} ll lowbit(ll x){return x&-x;} void change(ll x, ll v){while(x<=n){t[x]+=v;x+=lowbit(x);}} ll query(ll x){ll res=0;while(x){res+=t[x];x-=lowbit(x);}return res;} ll get(ll x){ ll l=1,r=n+1,res; while(l<r){ ll mid=l+r>>1,num=query(mid-1); if(num>x) r=mid; else l=mid+1; } return l-1; } void solve(){ for(int i=1; i<=n; i++) t[i]=0; n=read(); for(int i=1; i<=n; i++) change(i,1); for(int i=1; i<=n; i++){ ll x=read(); a[i]=get(x); change(a[i],-1); } for(int i=1; i<=n; i++){ write(a[i]); if(i!=n) putchar(' '); }putchar('\n'); } int main(){ ll T=read();while(T--) solve(); return 0; } ```