上海市计算机学会竞赛平台2023年7月月赛丙组题目T5 排列排序
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;
}
                        

                
            
        
浙公网安备 33010602011771号