编码I(2021-08-17)

题目大意

外网进不去
输入两个自然数n,m,输出n个数(1~n)的第m种全排列。

解题思路

最好想到的方法,就是使用暴力,枚举出1~n的全排列,再输出第m种排列。但这题的数据规模不允许: 1 ≤ n ≤ 20 1\le n\le 20 1n20 1 ≤ m ≤ n ! 1\le m\le n! 1mn!最糟糕的情况需要循环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](n1)!+a[n1](n2)!+...+a[1](11)!(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(51)!
再看1,在序列中,比一小且没用过的有0个,所以一是第零小的(从0开始编号),所以:
s = s + 0 ∗ ( 4 − 1 ) ! s=s+0*(4-1)! s=s+0(41)!
再看2,在序列中,比二小且没用过的有0个,所以二是第零小的(从0开始编号),所以:
s = s + 0 ∗ ( 3 − 1 ) ! s=s+0*(3-1)! s=s+0(31)!
再看4,在序列中,比四小且没用过的有0个,所以四是第零小的(从0开始编号),所以:
s = s + 0 ∗ ( 2 − 1 ) ! s=s+0*(2-1)! s=s+0(21)!
再看5,在序列中,比五小且没用过的有0个,所以五是第零小的(从0开始编号),所以:
s = s + 0 ∗ ( 1 − 1 ) ! s=s+0*(1-1)! s=s+0(11)!
由此,我们可以得到, 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) (491)/(51)!=2(......0)个未出现的数,所以第一个数为:3;
第二个数是当前(从0算起)第 0 / ( 4 − 1 ) ! = 0 ( . . . . . . 0 ) 0/(4-1)!=0(......0) 0/(41)!=0(......0)个未出现的数,所以第二个数为:1;
第三个数是当前(从0算起)第 0 / ( 3 − 1 ) ! = 0 ( . . . . . . 0 ) 0/(3-1)!=0(......0) 0/(31)!=0(......0)个未出现的数,所以第三个数为:2;
第四个数是当前(从0算起)第 0 / ( 2 − 1 ) ! = 0 ( . . . . . . 0 ) 0/(2-1)!=0(......0) 0/(21)!=0(......0)个未出现的数,所以第四个数为:4;
第五个数是当前(从0算起)第 0 / ( 1 − 1 ) ! = 0 ( . . . . . . 0 ) 0/(1-1)!=0(......0) 0/(11)!=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

猜你喜欢

转载自blog.csdn.net/weixin_41247488/article/details/119747025