hdu多校第五场1005 (hdu6628) permutation 1 排列/康托展开/暴力

本文探讨了如何寻找n个数排列中差分的字典序第k小的排列,通过分析发现,当k小于等于(n-1)!时,排列首位固定为n,其余部分为1至n-1的字典序排列。文中详细介绍了暴力打表与康托逆展算法的应用,并提供了完整的代码实现。

题意:

定义一个排列的差分为后一项减前一项之差构成的数列,求对于n个数的排列,差分的字典序第k小的那个,n<=20,k<=1e4。

题解:

暴力打表找一遍规律,会发现,对于n个数的排列,如果想找到差分的字典序第k小的,如果k<=(n-1)!,那么对应的那个排列就是把第一位赋值为n,后面的是1~n-1的元素本身排列字典序第k小的。

比如,4个元素的排列的差分字典序最小的前6个分别是

4,1,2,3

4,1,3,2

4,2,1,3

4,2,3,1

4,3,1,2

4,3,2,1

当n为10或更多的时候,(n-1)!>1e4,便可用康托逆展开直接计算。

当n为9以下时,原先的想法是在本地暴力排序,对于每个n都取前1e4个元素交表。后来发现hdu限制提交代码大小,分析了一下,9!约等于3e5,暴力打表,过了。

#include<iostream>
#include<vector>
#include<algorithm>
#define LL long long
using namespace std; 
int MAXN;
LL fact[25];
int tmp[30];
struct Node{
    int px[12];//排列
    int cha[12];//排列的差分
    int indexpx;
    int indexcha;
    friend bool operator > (const Node &a,const Node &b){
        for(int i=1;i<MAXN;i++){
            if(a.cha[i]!=b.cha[i])return a.cha[i]>b.cha[i];
        }
    }
    friend bool operator < (const Node &a,const Node &b){
        for(int i=1;i<MAXN;i++){
            if(a.cha[i]!=b.cha[i])return a.cha[i]<b.cha[i];
        }
    }
    //比较差分的字典序
}node[10000000];
int ans[10][100005];
void make_ans(){
    for(int i=1;i<=MAXN;i++){
        node[1].px[i]=i;
        node[1].cha[i-1]=1;
    }
    node[1].indexpx=1;
    int i,n;
    for(i=2;;i++){
        for(int j=1;j<=MAXN;j++){
            node[i].px[j]=node[i-1].px[j];
        }
        if(!next_permutation(&node[i].px[1],&node[i].px[MAXN+1])){
            n=i-1;
            break;
        }
        
        for(int j=1;j<MAXN;j++){
            node[i].cha[j]=node[i].px[j+1]-node[i].px[j];
        }
        
        node[i].indexpx=i;
    }
    sort(node+1,node+1+n);
    for(int i=1;i<=min(n,10000);i++){
        ans[MAXN][i]=node[i].indexpx;
    }
    //暴力打表预处理
}
void make_fact(){
    fact[0]=1;
    for(int i=1;i<=20;i++){
        fact[i]=fact[i-1]*i;
    }
}
void Cantor_invexp(int *p,int len,LL rank){
    //康托逆展开
    int temp[len];
    for(int i=0;i<len;i++){
        temp[i]=i+1;
    }
    for(int i=1;i<=len;i++){
        int a=rank/fact[len-i];
        rank%=fact[len-i];
        for(int j=0;j<len;j++){
            if(a==0 && temp[j]>0){
                p[i]=temp[j];
                temp[j]=0;
                break;
            }else if(temp[j]>0){
                a--;
            }
        }
    }
    return ;
}

int main(){
    make_fact();
    for(int i=2;i<=9;i++){
        MAXN=i;
        make_ans();
    }
    
//    return 0;
    int t;
    scanf("%d",&t);
    while(t--){
        int n,k;
        scanf("%d %d",&n,&k);
        if(n>=10){
            printf("%d",n);
            Cantor_invexp(tmp,n-1,1LL*k-1);
            for(int i=1;i<=n-1;i++){
                printf(" %d",tmp[i]);
            }
            printf("\n");
        }else{
            Cantor_invexp(tmp,n,1LL*ans[n][k]-1);
            for(int i=1;i<=n;i++){
                printf("%d",tmp[i]);
                if(i<n)printf(" ");
                else printf("\n");
            }
        }
    }
    return 0;
}

转载于:https://www.cnblogs.com/isakovsky/p/11305964.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值