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);
        }
    }
}

 

posted @ 2018-07-23 16:51  zZ1358m  阅读(368)  评论(0编辑  收藏  举报