AT2830 辞書順で何番目?题解


传送门

康托展开【模板】

思路

本题用了康托展开。

康托展开表达式:$ans=1+\sum\limits_{i=1}^n A\lbrack i\rbrack\times(n-i)!$

康托展开的主要思想是将所有的情况按顺序编一个号,并用简便的算法快速找出它的顺序。

举个例子:1 4 2 3

还没有出现过的数(从小到大):1 2 3 4

序列第一位是 1,在还没用过的序列中排第一个所以 $ans=ans+0\times3!$

还没有用过的数(从小到大):2 3 4

序列第一位是 4,在还没用过的序列中排第三个所以 $ans=ans+2\times2!$

还没有用过的数(从小到大):2 3

序列第一位是 2,在还没用过的序列中排第一个所以 $ans=ans+0\times1!$

还没有用过的数(从小到大):3

序列第一位是 2,在还没用过的序列中排第一个所以 $ans=ans+0\times0!$

此时 $ans=4$,根据公式再加一即是结果。

康托展开的查找

根据上面的例子,可能会有疑惑,为什么会出现阶乘?

其实是因为全排列问题,例如还剩 3 个数,有 3 个空共有 $3\times2\times1=6$ 种情况。

这也是前面提过的,为了给每种情况一种编号,而我们是通过从小到大的顺序进行编号的,那么我们可以
通过每一位的判断将这个位数之前的所有情况都算出来,就能知道当前的排名,在最后一位求完时,再加一就是当前排列的排名了。

讲得通俗一点就是,例如在自然数中,你要找到 100 这个数的排名,你可以通过所有可能的排序情况,先求出 99 的排名,最后再加 1 就是 100 的排名了。

所以通过每一位确定的数,求比当前序列已经确定的数组成的序列小的序列排名,最后加一就是当前序列的排名,这就是康托展开的求排序方法。

实现查找当前数位上的数在未用过数的序列中的排名

方法一:通过平衡树找当前数排名,注意及时删点(update)。

方法二:通过树状数组找到当前数的排名(本篇题解的代码就是这个思路)。

细节优化

代码

#include<iostream>
#include<cstdlib>
#include<cstring>
#include<cstdlib>
#include<cmath>
#include<algorithm>
using namespace std;
long long tree[1000005];
int n;
int lowbit(int x){//树状数组快速找"儿子" 
  return x&-x;
}
void update(int x,int y){//更新 
  while(x<=n){
    tree[x]+=y;
    x+=lowbit(x); 
  }
}
long long query(int x){//查询排名 
  long long sum=0;
  while(x){
    sum+=tree[x];
    x-=lowbit(x); 
  }
  return sum;
}
const long long o_o=1e9+7;//取模 
long long k_t[1000005]={1,1};
int a[1000005];
int main(){
  scanf("%d",&n);
  for(int i=1;i<=n;i++){//预处理阶乘 
    k_t[i]=(k_t[i-1]*i)%o_o;
    update(i,1);
  }
  long long ans=0;
  for(int i=1;i<=n;i++){
    scanf("%d",&a[i]);
    ans=(ans+((query(a[i])-1)*k_t[n-i])%o_o)%o_o;//康托展开 
    update(a[i],-1);//更新排名 
  }
  printf("%lld",ans+1);
   return 0;
}

文章作者: 王大神——A001
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 王大神——A001 !
  目录