康托展开

ouyu69 发布于 2024-12-30 37 次阅读


康托展开

康托展开是一个全排列到自然数的映射。康托展开的实质是计算当前排列在所有由小到大全排列中的顺序,因此是可逆的。

公式:

  • 表示在位置 后面并且小于 的值的个数。

比如所 的康托展开就是

也就是说这是第 个全排列(第一个全排列的康托展开是 )。

int n, m, k;
int a[N] ;//排列
int fac[N] ;//阶乘
int b[N] ;//树状数组
int lowbit(int x){
    return x & -x ;
}
void add(int x, int k){
    while(x <= n){
        b[x] += k ;
        x += lowbit(x) ;
    }
}
int querry(int x){
    int t = 0 ;
    while(x){
        t += b[x] ;
        x -= lowbit(x) ; 
    }
    return t ;
}
void solve(){
    cin >> n ;
    for(int i = 1; i <= n ; ++ i){
        cin >> a[i] ;
    }
    ans = 1 ;
    for(int i = n ; i >= 1; -- i ){//倒着来进行公式
        int num = querry(a[i]) ;
        ans = (ans +  num * fac[n-i] % MOD) % MOD ;
        add(a[i],1) ;
    }
    cout << ans << endl ;

}
signed main(){
    IOS;
    fac[1] = 1 ;
    for(int i = 2; i<= 1e6 ; ++ i){
        fac[i] = fac[i-1] * i % MOD;
    }
    while (T--){
        solve();
    }
    return 0;
}

 

逆康托展开

还以 为例,

  • ,商 ,说明在第一位后面只有一个比该为小的数,呢么就说明该位是第 小的数,即
  • ,商 ,说明该位置后没有比当前位置还小的数,所以就填当前能填的最小的数,即
  • ,商 ,说明该位置后有一个比当前位置还小的数,,所以就填当前能填的第 小的数,即
  • 最后剩下一个数 直接填到末尾即可。
int n, m, k;
int a[N] ;
int fac[N] ;
void solve(){
    cin >> n >> k ; //长度和位于排列中的第几个数
    k -- ;
    vector<int> vec ;
    vector<int> res ;
    for(int i = 1 ; i <= n ; ++ i) vec.push_back(i) ;
    for(int i = n ; i > 1; -- i){
        int pos = 0 ;
        if(i-1){
            pos = k / fac[i-1] ;
            k %= fac[i-1] ;
        }
        res.push_back(vec[pos]) ;
        vec.erase(vec.begin() + pos) ;
    }
    res.push_back(vec[0]) ;

    for(auto e : res){
        cout << e << " " ;
    }
    cout << endl ;

}
signed main(){
    IOS;
    fac[1] = 1 ;
    for(int i = 2; i<= 1e6 ; ++ i){
        fac[i] = fac[i-1] * i % MOD;
    }
    while (T--){
        solve();
    }
    return 0;
}

 

我打算法竞赛,真的假的。
最后更新于 2024-12-30