题解:UVA11525 Permutation

UVA11525 题解

题面

原题传送门

前置知识

正向康托展开

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

先那一组数据来手玩:

4 2 3 5 1 的排名。

  • 第一位是 \(4\),在这这个排列中,还会有以 \(1,2,3\) 作为第一位,所以它会增加 \(3\times(5-1)!=72\) 的排名。
  • 第二位是 \(2\),在以 4 为第一位的排列中,还会有 \(1\) 作为第二位,所以它会增加 \(1\times(5-2)!=6\) 的排名。
  • 第三位是 \(3\),在以 4 2 为前两位的排列中,还会有 \(1\) 作为第三位,所以他会增加 \(1\times(5-3)!=2\) 的排名。
  • 第四位是 \(5\),在以 4 2 3 为前三位的排列中,还会有 \(1\) 作为第四位,所以它会增加 \(1\times(5-4)!=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\)

\(S_i\) 就为比 \(i\) 小并且未被确定的数的个数。

在逐步确定 \(a\) 过程中,对于每个 \(i\in\{1,2,\cdots,n\}\),比 \(i\) 小并且未被确定的数的个数满足单调性,于是就可以二分求 \(a_i\),然后标上 \(a_i\) 已被标记(在树状数组中把 \(a_i\) 位置减一)。

时间复杂度为 \(O(n\log^2 n)\),用线段树上二分可以优化到 \(O(n\log n)\),但是笔者太懒(菜)了,只打了树状数组(反正能过就行)。

实现细节

一定要记得每一行的最后不能打空格!!!

代码

#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;
}
posted @ 2025-01-29 16:06  naroto2022  阅读(16)  评论(0)    收藏  举报