關於康託展開式的問題我已經在第一篇文章提到了,需要的朋友可以點進去看看。
ACM題目之排列序數
康託逆展開式就是給你一個數組,然後讓你求出其全排列第n大的序列.
這裏借用百度百科給的大家解釋下
例1 {1,2,3,4,5}的全排列,並且已經從小到大排序完畢
(1)找出第96個數
首先用96-1得到95
用95去除4! 得到3餘23
有3個數比它小的數是4
所以第一位是4
用23去除3! 得到3餘5
有3個數比它小的數是4但4已經在之前出現過了所以第二位是5(4在之前出現過,所以實際比5小的數是3個)
用5去除2!得到2餘1
有2個數比它小的數是3,第三位是3
用1去除1!得到1餘0
有1個數比它小的數是2,第二位是2
最後一個數只能是1
所以這個數是45321
(2)找出第16個數
首先用16-1得到15
用15去除4!得到0餘15
用15去除3!得到2餘3
用3去除2!得到1餘1
用1去除1!得到1餘0
有0個數比它小的數是1
有2個數比它小的數是3 但由於1已經在之前出現過了所以是4(因爲1在之前出現過了所以實際比4小的數是2)
有1個數比它小的數是2 但由於1已經在之前出現過了所以是3(因爲1在之前出現過了所以實際比3小的數是1)
有1個數比它小得數是2 但由於1,3,4已經在之前出現過了所以是5(因爲1,3,4在之前出現過了所以實際比5小的數是1)
最後一個數只能是2
所以這個數是14352。
code:
//康託逆展開式
//求n個數的全排列中第m大的組合:
//n>20就需要用高精度來做
#include <iostream>
#include<cstdio>
#include<cstring>
using namespace std;
int fact[10]= {1};
int res[10];
int n,m;
int main()
{
for(int i=1; i<=10; ++i)
fact[i]=fact[i-1]*i; //階乘數組
while(scanf("%d%d",&n,&m)!=EOF)
{
bool *vis=new bool[n];
memset(res,0,sizeof(res));
memset(vis,false,sizeof(vis));
//先判斷下m是否比n!還要大
if(m>fact[n]){cout<<"impossible"<<endl;continue;}
int i,j,x,t;
m--;
for(i=1; i<=n; ++i)
{
x=m/fact[n-i];
m%=fact[n-i];
for(j=1,t=0; t<=x; j++)
if(!vis[j])t++;
j--;
res[i-1]=j;
vis[j]=true;
}
for(i=0; i<n; ++i)
printf("%d",res[i]);
printf("\n");
}
// cout << "Hello world!" << endl;
return 0;
}