上海市计算机学会竞赛平台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 1k1000
60% 的数据 1 ≤ k ≤ 1 , 000 , 000 1≤k≤1,000,000 1k1,000,000
100% 的数据 1 ≤ k ≤ 1 0 15 1≤k≤10^{15} 1k1015
样例数据
输入:
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)! (i1)! )。

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)! (pos1)! 种可能,而它是第 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(c1)(s/pos) 就是它在长度为 p o s − 1 pos-1 pos1 的全排列中的位置。
然后设计递归。注意我是第…个数字,而不是…数字。因为当有的数字已经被使用后,不能再次使用,必须继续向后找。

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;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

GaoGuohao2022

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值