题目大意
外网进不去
输入两个自然数n,m,输出n个数(1~n)的第m种全排列。
解题思路
最好想到的方法,就是使用暴力,枚举出1~n的全排列,再输出第m种排列。但这题的数据规模不允许: 1 ≤ n ≤ 20 1\le n\le 20 1≤n≤20, 1 ≤ m ≤ n ! 1\le m\le n! 1≤m≤n!最糟糕的情况需要循环20!次,很明显会超时。不能用暴力,只能换一种思路——康托逆展开。
算法介绍
康托逆展开是康托展开的逆用。康托展开是用来给一个排列编序号的(不过是从0开始),公式为:
x
=
a
[
n
]
∗
(
n
−
1
)
!
+
a
[
n
−
1
]
∗
(
n
−
2
)
!
+
.
.
.
+
a
[
1
]
∗
(
1
−
1
)
!
x=a[n]*(n-1)!+a[n-1]*(n-2)!+...+a[1]*(1-1)!
x=a[n]∗(n−1)!+a[n−1]∗(n−2)!+...+a[1]∗(1−1)!(a[n]表示n是没有用过的数字里第几小的,也是从0开始)
如:3,1,2,4,5
先看3,在序列中,比三小且没用过的有2个,所以三是第二小的(从0开始编号),所以:
s
=
s
+
2
∗
(
5
−
1
)
!
s=s+2*(5-1)!
s=s+2∗(5−1)!
再看1,在序列中,比一小且没用过的有0个,所以一是第零小的(从0开始编号),所以:
s
=
s
+
0
∗
(
4
−
1
)
!
s=s+0*(4-1)!
s=s+0∗(4−1)!
再看2,在序列中,比二小且没用过的有0个,所以二是第零小的(从0开始编号),所以:
s
=
s
+
0
∗
(
3
−
1
)
!
s=s+0*(3-1)!
s=s+0∗(3−1)!
再看4,在序列中,比四小且没用过的有0个,所以四是第零小的(从0开始编号),所以:
s
=
s
+
0
∗
(
2
−
1
)
!
s=s+0*(2-1)!
s=s+0∗(2−1)!
再看5,在序列中,比五小且没用过的有0个,所以五是第零小的(从0开始编号),所以:
s
=
s
+
0
∗
(
1
−
1
)
!
s=s+0*(1-1)!
s=s+0∗(1−1)!
由此,我们可以得到,
s
=
48
s=48
s=48,也就是3,1,2,4,5这个排列的序号。
c++代码实现:
int kt()
{
int ans=0;
int t[101];
for(int i=0;i<=tot;i++)
t[i]=1;//标记所有都没有找过
for(int i=1;i<=tot;i++){
long long v2=0;
for(int j=1;j<a_t[i];j++)
v2+=t[j];//找到出现次数
t[a_t[i]]=0;
ans=ans+fact[v2]*(tot-i);//带人公式
}
return;
}
而康托逆展开就是利用一个编号(从0开始),来推出其排列:
如:设
s
=
49
,
l
e
n
t
h
=
5
s=49,lenth=5
s=49,lenth=5
第一个数是当前(从0算起)第
(
49
−
1
)
/
(
5
−
1
)
!
=
2
(
.
.
.
.
.
.
0
)
(49-1)/(5-1)!=2(......0)
(49−1)/(5−1)!=2(......0)个未出现的数,所以第一个数为:3;
第二个数是当前(从0算起)第
0
/
(
4
−
1
)
!
=
0
(
.
.
.
.
.
.
0
)
0/(4-1)!=0(......0)
0/(4−1)!=0(......0)个未出现的数,所以第二个数为:1;
第三个数是当前(从0算起)第
0
/
(
3
−
1
)
!
=
0
(
.
.
.
.
.
.
0
)
0/(3-1)!=0(......0)
0/(3−1)!=0(......0)个未出现的数,所以第三个数为:2;
第四个数是当前(从0算起)第
0
/
(
2
−
1
)
!
=
0
(
.
.
.
.
.
.
0
)
0/(2-1)!=0(......0)
0/(2−1)!=0(......0)个未出现的数,所以第四个数为:4;
第五个数是当前(从0算起)第
0
/
(
1
−
1
)
!
=
0
(
.
.
.
.
.
.
0
)
0/(1-1)!=0(......0)
0/(1−1)!=0(......0)个未出现的数,所以第五个数为:5;
所以,编号为49(从1算起)的排序为3,1,2,4,5;
c++代码实现:
void kt(long long n2)
{
for(int i=0;i<=n;i++)
t[i]=1;//记录所有都没有选过
long long t2;
n2--;
for(int i=0;i<n;i++){
t2=n2/fact[n-1-i];//得到商
n2%=fact[n-1-i];//得到余数
for(int j=1;j<=n;j++) {
if(!t[j])
continue;
if(!t2){//找到了第t2个数
t[j]=0;
a[i]=j;
break;
}
t2--;
}
}
}
代码实现
#include<bits/stdc++.h>
using namespace std;
long long a[100],m,n,fact[100],t[100],ans[100];
void ycl()
{
fact[0]=1;
for(int i=1;i<=n;i++)
fact[i]=fact[i-1]*i;
return;
}
void kt(long long n2)
{
for(int i=0;i<=n;i++)
t[i]=1;
long long t2;
n2--;
for(int i=0;i<n;i++){
t2=n2/fact[n-1-i];
n2%=fact[n-1-i];
for(int j=1;j<=n;j++) {
if(!t[j])
continue;
if(!t2){
t[j]=0;
a[i]=j;
break;
}
t2--;
}
}
}
int main()
{
cin>>n>>m;
ycl();
kt(m);
for(int i=0;i<n;i++)
cout<<a[i]<<" ";
}
样例
输入
3 2
输出
1 3 2