康拓展开

康拓展开(全排列排名)

思路

康拓展开是求一个全排列是第k小,思路很简单,我们求出比这个排列小的排列的数量,对于a[i],显然比a[i]小的数都可以放在这个位置,然后后面的位置随便放,方案数就是一个阶乘,当然,因为是全排列,为了避免重复使用数字,所以求的是在i的后面比a[i]小的数的个数。即$rk=b_n(n-1)!+b_{n-1}(n-2)!+…b_10!$,其中$b_i$就是表示在i的后面比a[i]小的数的个数。

朴素做法是O(N^2),可以用树状数组/线段树来优化计算$b_i$,复杂度为O(NlogN)。

代码

int cantor(vector<int>& perm){
    int n=perm.size();
    int ans=0;
    for(int i=0;i<n;i++){
        int cnt=0;
        for(int j=i+1;j<n;j++){
            if(perm[j]<perm[i]){
                cnt++;
            }
        }
        ans+=cnt*fac[n-1-i];
    }
    return ans+1;
}

逆康拓展开(第k个全排列)

思路

逆康拓展开是指求第k个全排列,是康拓展开的逆过程,我们依次确定每一位,通过除以阶乘计算有多少个数比a[i]小,通过取模得到下一位所对应的排名,同样的,为了避免重复使用数字,我们用一个标记数组来维护还没用过的数字。

朴素做法是O(N^2),可以用树状数组/线段树/主席树/平衡树来维护未使用的数字,复杂度为O(NlogN)。

代码

vector<int> revCantor(int n,int k){
    vector<int> vis(n+1,0);
    vector<int> ans(n,0);
    int m=k-1;
    for(int i=0;i<n;i++){
        int t=m/fac[n-1-i];
        m-=t*fac[n-1-i];
        for(int j=1;j<=n;j++){
            if(!vis[j]){
                t--;
                if(t==-1){
                    ans[i]=j;
                    vis[j]=1;
                    break;
                }
            }
        }
    }
    return ans;
}

上/下一个全排列

思路

感性理解,肯定是在后面找一个比前面某一个位置大的数,交换,之后后面的部分转为顺序排列,为了使交换后的全排列尽量小,满足"下一个",这个在前面被交换的数应该尽量靠后,权重更小,然后逆向思维, 被交换我们尽量从为了比如1 4 2 5 6 3,

感性理解,下一个全排列肯定是从后往前找到一段满足递增一个,再不断递减到完的后缀,要交换的显然是这个递增的这个,记为x,因为递减就是表示字典序最大了,要交换过去的数就是从后面找到一个刚好大于x的数,记为y,swap(x,y),然后将后面递减部分逆序成为递增,类似于进位,后面要恢复到字典序最小的递增,为什么要从后往前,因为我们尽量让交换发生在权重更小的后面,满足下一个全排列的要求。

总结起来就是:

next_permutation的思路:从后往前找到一个i满足a[i]<a[i+1],此时a[i+1…n-1]是递减的,然后再次从后往前找到第一个大于a[i]的数,两数交换,此时a[i+1…n-1]仍然保持递减,将这一部分逆序即可。

prev_permutation的思路:累死next_permutation,符号相反,即从后往前找到一个i满足a[i]>a[i+1],此时a[i+1…n-1]是递增的,然后再次从后往前找到第一个小于a[i]的数,两数交换,此时a[i+1…n-1]仍然保持递增,将这一部分逆序即可。

代码

STL库函数

prev_permutation(a,a+n);
next_permutation(a,a+n);
vector<int> next_permutation(vector<int>& perm){
    int n=perm.size();
    int idx=-1;
    for(int i=n-1;i>0;i--){
        if(perm[i]>perm[i-1]){
            idx=i-1;
            break;
        }
    }
    if(idx!=-1){
        for(int i=n-1;i>idx;i--){
            if(perm[i]>perm[idx]){
                swap(perm[i],perm[idx]);
                break;
            }
        }
    }
    int l=idx+1;
    int r=n-1;
    while(l<r){
        swap(perm[l++],perm[r--]);
    }
    return perm;
}
vector<int> prev_permutation(vector<int>& perm){
    int n=perm.size();
    int idx=-1;
    for(int i=n-1;i>0;i--){
        if(perm[i]<perm[i-1]){
            idx=i-1;
            break;
        }
    }
    if(idx!=-1){
        for(int i=n-1;i>idx;i--){
            if(perm[i]<perm[idx]){
                swap(perm[i],perm[idx]);
                break;
            }
        }
    }
    int l=idx+1;
    int r=n-1;
    while(l<r){
        swap(perm[l++],perm[r--]);
    }
    return perm;
}