T5 排列排序
内存限制: 256 Mb时间限制: 1000 ms
题目描述
如果一个整数序列 a 1 , a 2 , … , a n a_1,a_2 ,…,a_n a1,a2,…,an 的每个数字都在 1 到 n 之间,且没有两个数字相等,则称这个序列为全排列。例如1,3,2 以及 4,3,2,1 都是全排列。
我们将所有的全排列排序,定义全排列 a 1 , a 2 , … , a n a_1,a_2,…,a_n a1,a2,…,an 与 b 1 , b 2 , … , b m b_1,b_2,…,b_m b1,b2,…,bm 的排序先后关系如下:
如果 n < m n<m n<m,则 a 序列更靠前
如果 n > m n>m n>m,则 b 序列更靠前
如果 n = m n=m n=m,则以字典序规则比较 a 序列与 b 序列,字典序更小的序列更靠前。
根据上述定义,可以得到
第 1 个全排列是 1
第 2 个全排列是 1 2
第 3 个全排列是 2 1
第 4 个全排列是 1 2 3
给定 k k k,请输出第 k k k 个全排列。
输入格式
单个整数:表示 k k k
输出格式
单独一行:表示第 k k k 个全排列
数据范围
30% 的数据 1 ≤ k ≤ 1000 1≤k≤1000 1≤k≤1000
60% 的数据 1 ≤ k ≤ 1 , 000 , 000 1≤k≤1,000,000 1≤k≤1,000,000
100% 的数据 1 ≤ k ≤ 1 0 15 1≤k≤10^{15} 1≤k≤1015
样例数据
输入:
5
输出:
1 3 2
说明:
根据上述定义可知全排列
a
1
,
a
2
,
.
.
.
,
a
n
a_1,a_2,...,a_n
a1,a2,...,an 中的填数方法有
A
n
n
A^n_n
Ann(
n
!
n!
n!) 种,更通俗地说,长度为
n
n
n 的全排列的个数有
n
!
n!
n! 个。
那么已知
k
k
k ,那么不断用
k
k
k 减去
1
!
,
2
!
,
3
!
,
.
.
.
,
c
n
t
!
1!,2!,3!,...,cnt!
1!,2!,3!,...,cnt!直至结果在保证为正的情况下无法再减,那么
c
n
t
cnt
cnt 就是第
k
k
k 个全排列的位数,假设下列代码所用变量已经定义,最终可以求出第
k
k
k 个全排列是位数为
p
o
s
pos
pos 的全排列中的第
k
k
k (两个
k
k
k 不一样)个,其中
i
i
i 存放的是位数(即当前阶乘的参数),
s
s
s 存放阶乘之和,
l
a
s
t
last
last 存放上一个的阶乘(即
(
i
−
1
)
!
(i-1)!
(i−1)! )。
int i=0;
while(++i){
s+=last*i;
if(s>k){
k=k-s+last*i;
pos=i;
s=last*i;
break;
}
last*=i;
}
当我们知道它是位数为
p
o
s
pos
pos 的全排列中的第
k
k
k 个时,而
s
s
s 表示
p
o
s
!
pos!
pos!,我们可以确定第一位,因为第一位有
p
o
s
pos
pos 种可能,而总计有
p
o
s
!
pos!
pos! 种可能,所以每一种
p
o
s
pos
pos 的可能性都对应着
(
p
o
s
−
1
)
!
(pos-1)!
(pos−1)! 种可能,而它是第
k
k
k 种,所以第一位是第
k
/
(
s
/
p
o
s
)
k/(s/pos)
k/(s/pos) (向上取整)个数字。假设第一位是第
c
c
c 个数字,去掉前面的
c
∗
(
s
/
p
o
s
)
c*(s/pos)
c∗(s/pos) 种可能,剩下的
k
−
(
c
−
1
)
∗
(
s
/
p
o
s
)
k-(c-1)*(s/pos)
k−(c−1)∗(s/pos) 就是它在长度为
p
o
s
−
1
pos-1
pos−1 的全排列中的位置。
然后设计递归。注意我是第…个数字,而不是…数字。因为当有的数字已经被使用后,不能再次使用,必须继续向后找。
void f(int pos,int s,int k)
p
o
s
pos
pos 表示位数,当其等于1时不再递归;
s
s
s 表示
p
o
s
!
pos!
pos! 用于计算可能性数量,注意实时的更新;
k
k
k表示的是位次,用于每次输出剩余的首位(即第
总位数
−
p
o
s
+
1
总位数-pos+1
总位数−pos+1 位)。
至于计算第
x
x
x 个数字,应当使用
v
h
vh
vh 数组进行标记,如果使用过了,即继续查找。
int i=0,cnt=(k+s/pos-1)/(s/pos);
while(cnt--)
if(vh[++i])++cnt;
cout<<i<<" ";
最后说明:因为没有仔细估计题目数据,数组就开到了10000000,具体大家可以算一算 1 0 15 10^{15} 1015 是 1 ! + 2 ! + 3 ! + . . . + n ! 1!+2!+3!+...+n! 1!+2!+3!+...+n!中的 n n n 值,最终算法复杂度为 O ( n 2 ) O(n^2) O(n2)(最坏情况)。
#include <bits/stdc++.h>
#define int long long
using namespace std;
int k,s,last=1,pos,i;
bool vh[10000000];
void f(int pos,int s,int k){
int i=0,cnt=(k+s/pos-1)/(s/pos);
int c=cnt;
while(cnt--)
if(vh[++i])++cnt;
cout<<i<<" ";
vh[i]=1;
if(pos>1)f(pos-1,s/pos,/*k-(c-1)*(s/pos)*/k%(s/pos)?k%(s/pos):s/pos);//注释中是第二种参数写法
}
signed main(){
cin>>k;
while(++i){
s+=last*i;
if(s>k){
k=k-s+last*i;
pos=i;
s=last*i;
break;
}
last*=i;
}
f(pos,s,k);
return 0;
}