zoukankan      html  css  js  c++  java
  • 洛谷 P5367 【模板】康托展开(数论,树状数组)

    题目链接

    https://www.luogu.org/problem/P5367

    什么是康托展开

    百度百科上是这样说的:

     
    康托展开是一个全排列到一个自然数的双射,常用于构建哈希表时的空间压缩。 康托展开的实质是计算当前排列在所有由小到大全排列中的顺序,因此是可逆的。

    是不是讲得很精(meng)(bi)呢?

    我看了无数篇博客,终于明白了一点点。

    其实,康托展开就是求一个全排列在所有全排列中字典序排名第几

    举个例子:

    比如说n=3的一个全排列:2 1 3 它的排名是3。

    我们列出所有的全排列:

    1 2 3

    1 3 2

    2 1 3

    2 3 1

    3 1 2

    3 2 1

    显然,2 1 3在里面字典序排名第三。

    暴力求法(基本思路)

    首先我们用a[i]表示原数的第i位在当前未出现的元素中是排在第几个

    比如说  "2 3 4 1"

    a[1]=2    a[2]=2    a[3]=2    a[4]=0

    拿a[2]举例子,到第二位时,未出现的数字有1,3,4,显然3排在第二位上,所以a[2]=2。

    然后我们想,在前k-1位相等的情况下,a[k]具有什么意义?比当前情况字典序小的全排列数有多少呢?

    显然是  a[k]*(k-1)!  (注意阶乘的优先级比乘法运算高)  哪里显然了QAQ?

    好像这叫做乘法原理来着(蒟蒻记不清楚了)

    a[k]是第k位的比原排列小的数字数量,而第k-1~n位无论是什么数一定小于原数列,而且每一位都要用掉一个数字,所以就是a[k]*(k-1)*(k-2)*(k-3)*……*2*1。

    最后把这些小于原排列的排列数量加起来,最后在+1就是原数列的排名。

    放公式:ans=a1*0+a2*(2-1)!+a3*(3-1)!+……+an*(n-1)!+1。

    时间复杂度为O(n^2)

    优化

    • 先预处理1到n的阶乘
    • 用树状数组来维护有多少个未出现的比自己小的数(单点修改,区间查询)——一开始所有点都修改为1,然后每遇到一个点,就修改为0,最后查询1~s[k-1]有多少个1就行了(s为原数列)。

    当然了,也可以用万能的线段树(只不过常数比较大罢了)

    AC代码

     1 #include<iostream>
     2 #include<cstdio>
     3 using namespace std;
     4 const int mod=998244353;
     5 const int maxn=1000005;
     6 int ss[maxn],a[maxn],s[maxn],n;
     7 inline int lowbit(int x){
     8     return x&(-x);
     9 }
    10 void update(int id,int x){
    11     for(int i=id;i<=n;i+=lowbit(i)){
    12         s[i]+=x;
    13     }
    14 }
    15 int query(int id){
    16     int res=0;
    17     for(int i=id;i>0;i-=lowbit(i)){
    18         res+=s[i];
    19     }
    20     return res;
    21 }
    22 long long ans,jc[maxn];
    23 int main()
    24 {
    25     cin>>n;
    26     jc[1]=1;
    27     for(int i=2;i<n;i++) jc[i]=jc[i-1]*i%mod;
    28     for(int i=1;i<=n;i++) scanf("%d",&ss[i]);
    29     for(int i=1;i<=n;i++) update(ss[i],1);
    30     for(int i=1;i<=n;i++){
    31         update(ss[i],-1);
    32         a[n-i+1]=query(ss[i]);
    33     }
    34     for(int i=1;i<=n;i++) ans=(ans+(long long)a[i]*jc[i-1]%mod)%mod;
    35     cout<<ans+1;
    36     return 0;
    37 }
  • 相关阅读:
    silverlight的TranslateTransform 的使用
    720 JavaScript函数的this指向
    JavaScript数组
    JavaScriptDOM事件
    JavaScript流程控制语句
    CSS布局案例
    JavaScriptDOM基础
    JavaScriptDOM事件
    JavaScript基础语法
    JavaScript的String对象相关方法
  • 原文地址:https://www.cnblogs.com/yinyuqin/p/11342460.html
Copyright © 2011-2022 走看看