zoukankan      html  css  js  c++  java
  • BZOJ3301 P2524 UVA11525 算法解释康托展开

    这三个题的代码分别对应第二个第一个第三个

    在刘汝佳蓝书上我遇到了这个康托展开题。

    当时去了解了一下,发现很有意思

    百度上的康托展开定义

    原理介绍

    编辑

    康托展开运算

    其中,
    为整数,并且
    的意义为在ai之后出现的数有几个比他小

    康托展开的逆运算

    既然康托展开是一个双射,那么一定可以通过康托展开值求出原排列,即可以求出n的全排列中第x大排列。
    如n=5,x=96时:
    首先用96-1得到95,说明x之前有95个排列.(将此数本身减去1)用95去除4! 得到3余23,说明有3个数比第1位小,所以第一位是4.用23去除3! 得到3余5,说明有3个数比第2位小,所以是4,但是4已出现过,因此是5.用5去除2!得到2余1,类似地,这一位是3.用1去除1!得到1余0,这一位是2.最后一位只能是1.所以这个数是45321。
    按以上方法可以得出通用的算法。 [1] 
     
    此定理的证明十分简易,就是用组合原理
    我们能明白第k位上的数码若为x则有(n-k-1)!(x-1)种比他小的排列(字典序小)
    就可以证了
     
     
    显然,n位(0~n-1)全排列后,其康托展开唯一且最大约为n!,因此可以由更小的空间来储存这些排列。由公式可将X逆推出唯一的一个排列。
     
     
    我们可以发现正着求的话,阶乘on预处理,那么关键就在于ai怎么求。
    我们观察到暴力是on2
    如果把数变为布尔数组
    转化问题为一般二位偏序
    用树状数组求前面0的个数
    不就是ai了么
    所以就可以nlogn求解
     
    发下暴力(洛谷2524)
     1 #include<iostream>
     2 #include<cstring>
     3 #include<cstdio>
     4 using namespace std;
     5 #define mod 19260817
     6 int n,m,a,b,c,ans=0,list[1000],visit[1000];
     7 void makelist(){
     8     list[1]=1;
     9     for(int i=2;i<=1000;i++)
    10     list[i]=(list[i-1]%mod)*i%mod;
    11 }
    12 string s;
    13 int main(){
    14     cin>>n;
    15     memset(visit,0,sizeof(visit));
    16     makelist();
    17     cin>>s;
    18     for(int i=0;i<s.length();i++){
    19         a=s[i]-'0';visit[a]=1;
    20         m=0;
    21         for(int j=1;j<a;j++){
    22             if(!visit[j])m++;
    23         }
    24         ans+=list[n-1-i]*m;
    25     }
    26     cout<<ans+1;
    27     return 0;
    28 } 

    轮到正解了(bzoj3301)

    #include<iostream>
    #include<cstring>
    #include<cstdio>
    using namespace std;
    #define mod 19260817
    int n,m,a,b,c,ans=0,list[100010],input[100010],tree[100010];
    int lowbit(int x){return x&(-x);}
    int query(int x){int ans=0;for(int i=x;i>0;i-=lowbit(i))ans+=tree[i];return ans;}
    void add(int p,int x){for(int i=p;i<=n;i+=lowbit(i))tree[i]+=x;}
    void makelist(){
        list[1]=1;
        for(int i=2;i<=1000;i++)
        list[i]=(list[i-1]%mod)*i%mod;
    }
    int main(){
        cin>>n;
        makelist();
        for(int i=1;i<=n;i++)
        cin>>input[i];
        for(int i=1;i<=n;i++){
            a=input[i];add(a,1);
            m=a-query(a); 
            ans+=list[n-i]*m;
        }
        cout<<ans+1;
        return 0;
    } 

    那逆运算呢

    由于我们知道ai<n

    所以我们观察到(n-i)!*ai<n!

    可以推出x/(n-i)!=ai;(下取整)

    问题转化为二位偏序,前缀第k大

    就可以用树状数组解决(也可以用主席树)

    代码参考的candy博主

    uva1125
    #include <iostream>
    #include <cstdio>
    #include <cstring>
    #include <algorithm>
    #include <cmath>
    using namespace std;
    const int N=5e5+5,INF=1e6+5;
    inline int read(){
        char c=getchar();int x=0,f=1;
        while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
        while(c>='0'&&c<='9'){x=x*10+c-'0';c=getchar();}
        return x*f;
    }
    int n,x,k;
    int c[N];
    inline int lowbit(int x){return x&-x;}
    inline void add(int p,int v){
        for(;p<=n;p+=lowbit(p)) c[p]+=v;
    }
    inline int sum(int p){
        int res=0;
        for(;p>0;p-=lowbit(p)) res+=c[p];
        return res;
    }
    inline int kth(int k){
        int x=0,cnt=0;
        for(int i=16;i>=0;i--){
            x+=(1<<i);
            if(x>=n||cnt+c[x]>=k) x-=(1<<i);
            else cnt+=c[x];
        }
        return x+1;
    }
    int main(){
        int T=read();
        while(T--){
            n=read();
            memset(c,0,sizeof(c));
            for(int i=1;i<=n;i++) add(i,1);
            for(int i=1;i<=n;i++){
                k=read()+1;
                x=kth(k);
                cout<<x<<" ";
                add(x,-1);
            }
        }
    }
  • 相关阅读:
    maven工程的目录结构
    集合的区别
    名词解析
    1.(字符串)-判断字符串是否是子集字符串
    1.(字符串)-判断两字符串是否相等
    python max函数技巧
    1.(字符串)-子字符串位置查找
    numpy线性代数np.linalg
    Python图像库PIL 使用
    pyhthon-chr
  • 原文地址:https://www.cnblogs.com/iboom/p/9355558.html
Copyright © 2011-2022 走看看