全排列散列 - (康托展开 和 逆康托展开)

注:代码很长,不要害怕,不难的,核心代码只有几行,贴上完整代码是为了大家测试方便。

首先明确我们要求的是什么样的题目。

例如,给定数组a[10] = {1,2,3,4,5,6,7,8,9,10};

我们把排列{1,2,3,4,5,6,7,8,9,10}规定为0

我们把排列{1,2,3,4,5,6,7,8,10,9}规定为1

......

现给定排列{2,3,5,1,4,6,8,7,9,10} 代表的是多少?

当然,我们可以通过递归求解a数组的全排列,并且计数并判断当前全排列是否是要求的全排列,如果是,那么输出结果。

我们知道当有n个数的时候他的全排列数字是n!个,当n较大时,通过递归(或者使用全排列函数)去枚举,时间效率是非常低的。那么怎么办呢?

有一个神奇的公式叫做康托展开式:X=a[n]*(n-1)!+a[n-1]*(n-2)!+...+a[i]*(i-1)!+...+a[1]*0! ,

其中a[i]为当前未出现的元素中是排在第几个(从0开始)。

那么我们通过模拟就可以很快的写出代码:


#include <cstdio> 
#include <iostream>
#include <map>
#include <set>
#include <vector>
#include <cmath>
#include <string>
#include <cstring>
#include <algorithm>
#define LL long long
#define MAXN  1000
using namespace std;
/*
全排列散列 康托展开 
*/
// 求阶乘 
LL fac(int x){
	LL ans = 1;
	for(int i = 2; i <= x; i++){
		ans *= i; 
	}
	return ans; 
}
LL ans;
int n;
int a[100];
void arrayToInt(){
	for(int i = 0; i < n; i++){
		int temp = 0;
		for(int  j = i+1; j < n; j++){
			if(a[j] < a[i]) temp++; 
		}
		ans += temp * fac(n-i-1);		 
	}
	printf("%lld\n",ans);
} 
int main(){
//	freopen("input.txt","r",stdin);

	scanf("%d",&n);
	for(int i = 0; i < n; i++){
		scanf("%d",&a[i]);
	}
	arrayToInt();
	return 0;
}

既然我们可以通过全排列求出这个全排列是第几个,

那么,如果我们知道某个全排列是第几个,是否也可以求出这个全排列是什么样的呢?

那我们将上面的公式倒推一下就可以。

叫做逆康托展开式(也有的称为康托逆展开式)。

即我们知道了X=a[n]*(n-1)!+a[n-1]*(n-2)!+...+a[i]*(i-1)!+...+a[1]*0!其中X 知道 求解a[ 1 ] - a[ n ]

注:X是从0开始

这里我们举个例子来说明:

已知序列1 2 3 4求编号为 3 的序列是什么(1 3 4 2)

3 / 3! = 0 余 3

3 / 2! = 0 余 3

3 / 1! = 3 余 0

0 / 0! (这里不操作)

(上一个算式的余数是下一个算式的被除数,每个算式的商是对应的a数组中算数)

由上可知,

比第一个数小的数有0个,

比第二个数小的有0个,

比第三个数小的有3个

即a[ 4 ] = 0, 所以第一个数是1

a[ 3 ] = 0, 因此1已经用过,所以第二个数是2a[ 2 ] = 3,

所以第三个数是4a[ 3 ] 最后剩下2 ,

所以第四个数字是2

同样的,我们只需要模拟算法的过程即可得到代码:


#include <cstdio> 
#include <iostream>
#include <map>
#include <set>
#include <vector>
#include <cmath>
#include <string>
#include <cstring>
#include <algorithm>
#define LL long long
#define MAXN  1000
using namespace std;
/*
全排列散列 康托展开 
*/
// 求阶乘 
LL fac(int x){
	LL ans = 1;
	for(int i = 2; i <= x; i++){
		ans *= i; 
	}
	return ans; 
}
LL ans;
int n;
int a[100];
int used[100];
void intToArray(int x){
	memset(used, 0, sizeof(used));
	int i, j, temp;
	for(i = 0; i < n; i++){
		temp = x / fac(n-i-1);  
		for(j = 1; j <= n; j++){
			if(!used[j]){
				if(temp == 0) break;
				temp --;			
			}
		}
		a[i] = j; used[j] = 1;
		x %= fac(n-i-1);
	}
	for(int i = 0; i < n; i++){
		printf("%d ",a[i]);	
	}
} 
int main(){
	freopen("input.txt","r",stdin);

	scanf("%d",&n);
	for(int i = 0; i < 10; i++){
		intToArray(i); puts("");
	}
	
	return 0;
}

康托展开是个特殊的哈希函数,当然他还有别的应用,,比如在搜索问题中,我们可以对vis数组进行压缩,如八数码问题。





评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值