problem
输入两个整数n和k,询问1~n的所有排列中,按字典序升序排在第k位的排列是什么
Input
输入两个整数n,k(2<=n<=18,1<=k<=n!)
Output
输出n个整数,表示字典序排在第k位的那个排列。
Input
3 4
Output
2 3 1
Limitation
1s 256MB
Hint
n=3的时候,所有的排列按照字典序升序排列如下:
[1,2,3]
[1,3,2]
[2,1,3]
[2,3,1]
[3,1,2]
[3,2,1]
所以第4位的排列是[2,3,1]
思路
康托展开:它是一个式子 与全排列有关
X=a[n](n-1)!+a[n-1](n-2)!+…+a[i]*(i-1)!+…+a[1]*0! n为排列元素个数
其中a[i]为当前未出现的元素中是排在第几个(从0开始计数)
例如有一个数组 s = [“A”, “B”, “C”, “D”]
它的一个排列 s1 = [“D”, “B”, “A”, “C”] 现在要把s1序列映射出对应的X
所以 n=4 X(s1) = a4*3! + a3*2! + a2*1! + a1*0!
a4为D a3为B a2为A a1为C
a4=3 a3=1 a2=0 a1=0(a1总是0) (从零计数)
X(s1) = 3*3! + 1*2! + 0*1! + 0*0! = 20
康托展开逆运算:如果已知 s = [“A”, “B”, “C”, “D”],X(s1) = 20,能否推出 s1 = [“D”, “B”, “A”, “C”] 呢?
因为已知
X(s1)=a4∗3!+a3∗2!+a2∗1!+a1∗0!=20
,所以问题变成由 20 能否唯一地映射出一组 a4、a3、a2、a1?如果不考虑 ai 的取值范围,有:
3∗3!+1∗2!+0∗1!+0∗0!=20
2∗3!+4∗2!+0∗1!+0∗0!=20
1∗3!+7∗2!+0∗1!+0∗0!=20
0∗3!+10∗2!+0∗1!+0∗0!=20
0∗3!+0∗2!+20∗1!+0∗0!=20
但是满足 0 <= ai <= n-1 的只有第一组,可以使用辗转相除的方法得到 ai
知道了a4、a3、a2、a1的值,就可以知道s1[0] 是子数组[“A”, “B”, “C”, “D”]中第3大的元素 “D”,s1[1] 是子数组 [“A”, “B”, “C”] 中第1大的元素”B”,s1[2] 是子数组 [“A”, “C”] 中第0大的元素”A”,s[3] 是子数组 [“C”] 中第0大的元素”C”,所以s1 = [“D”, “B”, “A”, “C”] (第几大从零计数)
代码示例
#include<bits/stdc++.h>
using namespace std;
long long f[20];//阶乘
void init()
{
f[0]=1;
f[1]=1;
for(int i=2;i<=19;++i)
f[i]=f[i-1]*i;
}
int main()
{
ios::sync_with_stdio(false);
init();
//cout<<f[18]<<endl;
long long n,k;//n是位数,k是第几个
long long v[20],num[20],i,j,t;
cin>>n>>k;
k--;
memset(v,0,sizeof(v));
for(int i=0;i<n;++i){
t=k/f[n-1-i];
for(j=0;j<n;++j){
if(!v[j]){
if(t==0) break;
t--;
}
}
num[i]=j;
v[j]=1;
k=k%f[n-1-i];
}
for(int i=0;i<n-1;++i) cout<<num[i]+1<<' ';
cout<<num[n-1]+1<<endl;
return 0;
}