题目大意是给出一个N个数字的排列,求出所有N个数的排列中,字典序(排列中每个数看作一个字符)
位于给出的排列前K个的那个排列。
算法思想:把第I个排列和整数对应起来,只需要把给定排列的对应整数编号计算出来,把它减去K,再
求出这个编号所对应的排列即可。
代码如下:
#include<cstdio> #include<algorithm> //using namespace std; /* 前K个排列问题 映射与逆映射问题(O(N)) 算法思想:把给出的排列对应的整数计算出来 把它减去K 再求出此编码所对应的排列即可。 具体算法:采用“康托展开”把一个排列对应 的字典排序的位置计算出来,也叫“字典法” 假设N=3 字典序列出来就是: 1,2,3;(1) 1,3,2;(2) 2,1,3;(3) 2,3,1;(4) 3,1,2;(5) 3,2,1;(6) 共计6种排列方式 */ ///由于对应的整数太大,采用64位整数表示 typedef _int64 int64; //a是输入排列,同时存储输出的结果 int a[35],i,N,K; ///p用来存储可变进制数的每一位权值 int64 s,p[35]; //获取排列a对应的编号 int64 get_per(int *a){ int i,j; int64 c,ret=0; ///计算可变进制数的值,这里是把排列倒过来算,因此排最前的排列对应的数是N!-1 ///排最后的排列对应的数是0 如正排列为:N=5 K=119 5,4,3,2,1(Id=4*4!+3*3!+2*2!+1*1!=199) //// 1,2,3,4,5(Id=0) 4,1,3,2,5(Id=3*4!+0*3!+1*2!+1*1!=75/Id=1*4!+3*3!+1*2!+1*1!=45)互补5!=120=75+45 //// 倒排列为:N=5 K=119 5,4,3,2,1(Id=0*4!+0*3!+0*2!+0*1!=0) //// 1,2,3,4,5(Id=199) 注:编号从0开始计算 正排列找的是当前位置后面比它小的个数,反之倒排列找的是比它大的 for(i=N-2;i>=0;i--){ c=0; for(j=i;j<N;j++) ////采用倒排列,比当前位置数字大的个数即为倒排列所对应数字 if(a[j]>a[i]) c++; //p[N-i-1]是对应的权值,c为权值前面的阶(数字) ret +=c*p[N-i-1]; }//for return ret; } ///根据一个整数key构造出对应的排列S void set_per(int *a,int64 key){ int i,h[30],j,k; memset(h,0,sizeof(h)); for(i=0;i<N;i++){ //解出可变进制数的某一位 for(k=0;key>=p[N-i-1];k++,key -= p[N-i-1]); //找出可变进制数据当前一位对应的数字 for(j=N;j>0;j--){ if(0==h[j]) k--; if(k<0) break; }//for a[i]=j;//设置第i位的数字为j h[j]=1;//设置第j位被占用 }//for } int main(){ p[0]=p[1]=1; ////预处理阶乘所对应的值,查字典方式获取 for(i=2;i<=20;i++) p[i]=i*p[i-1]; while(scanf("%d %d",&N,&K) && N){ for(i=0;i<N;i++) scanf("%d",&a[i]); s=get_per(a); ///判断是否超出排列的最大编号,超过即表示没有对应排列 if(s+K >= p[N]){ printf("-1\n"); continue; } else{ ////排列倒排列处理,前K个排列即为加K个的排列 s += K; set_per(a,s); } ////输出处理,是否为空格还是换行 for(i=0;i<N;i++) printf("%d%c",a[i],i==N-1?'\n':' '); } return 0; } /* 5 4 4 1 3 2 5 5 1 1 2 3 4 5 5 119 5 4 3 2 1 */