全排列问题
1.输出n的全排列
这个比较简单,用递归就行了
1 void permutation(int now){ 2 3 if(now > n){//如果已经排完 4 for (int i = 1; i <= n; i++) 5 printf("%d", a[i]); 6 printf("\n"); 7 return; 8 } 9 10 for(int i = 1; i <= n; i++){ 11 if(!used[i]){//如果i这个数没被用过 12 a[now] = i; 13 used[i] = 1; 14 permutation(now + 1);//排下一个 15 used[i] = 0;//回溯 16 } 17 } 18 return; 19 }
2.next_permutation
如何输出下一个全排列呢?
我们可以先观察几个排列
12345
12354
12435
12453
54321
可以发现,这列数在逐渐从正序变成倒序
显然,正序数<乱序数
那么,我们可以从右往左遍历,找到第一个非递减的数字,然后将它与它后面比大的最小数交换,再将后面的数正序排列
例如,对于24351,我们先找到4,将4和5交换,在将后面的数正序排列,得到
25134
1 void next_permutation(){ 2 int i = n, j; 3 while(a[i-1] > a[i] && i >= 1)i--;//找到第一个非逆序的数 4 if (i < 1)return; 5 i--; 6 for(j = n; a[j] < a[i]; j--);//找到最小的比a[i]大的数 7 swap(a[j], a[i]); 8 i++; 9 j = n; 10 while (i < j)swap(a[i++], a[j--]);//将后面逆序 11 return; 12 }
当然,algorithm库里也有现成的next_permutation函数
用法:
next_permutation(a+0, a + n) next_permutation(a.begin(h), a.end())
3.康托展开
康托展开用来求一个排列的字典序排名
设有排列 ,那么对任意字典序比
小的排列,一定存在
,使得其前
位与
对应位相同,第
位比
小,后续位随意。于是对于任意
,满足条件的排列数就是从后
位中选一个比
小的数、并将剩下
个数任意排列的方案数,即为
(
表示
后面比
小的数的个数)。遍历
即得总方案数:
再加1即为排名。
在求Ai的时候需要两层循环,因此,康托展开的时间复杂度为O(n^2)
树状数组优化
我们可以用树状数组或者线段树来以此记录前面每个数出现的次数,然后每次求出比它小的数出现的次数和,就能得到后面的比它小的数的个数了
这样,就能优化到O(nlogn)
4.逆康托展开
逆康托展开用来求已知排名的排列
不难发现,对于4......这样的一个排列,有[4*(n-1)! , 5*(n-1)!)个排列比它小
因此,
对于排名为x的n个数的排列
首先让x-=1
[x/(n-1)!]即为第一位的数字
然后让x减去[x/(n-1)!] *(n-1)!,[x/(n-2)!]即为第二位数字
以此类推
5.变进制数
由于排列的排名可能很大,因此,并不能用康托展开有效解决。
那么,整个过程分为3步:
- 将排列数变成变进制数
- 将变进制数加上m
- 将变进制数变成排列数
我们来看一个实例: 将 1,4,5,2,3变成变进制数
- 首位1是5种选择{1,2,3,4,5}的第1种,故变为0(从0开始)
- 次位4是4种选择{2,3,4,5}的第3种,故变为2
- 中间位5是3种选择{2,3,5}的第3种,故变为2
- 次低位2是2种选择{2,3}的第1种,故变为0
- 末位3是1种选择的{3}第1种,故变为0
- 最后,1,4,5,2,3变成了(02200)unknown
然后将它加上3,得(03010)unknown
最后将它变回火星数。
- 首位0表示这位应选择{1,2,3,4,5}第1种,即1
- 次位3表示这位应选择{2,3,4,5}第4种(1被选过了),即5
- 中间位0表示这位应选择{2,3,4}第1种,即2
- 次低位1表示这位应选择{3,4}第2种,即4
- 末位0表示这位应选择{3}第1种,即3
- 所以答案为“14523”+3=“15243”

浙公网安备 33010602011771号