全排列问题

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.变进制数

转载自洛谷P1088 yummy大佬的题解

 

由于排列的排名可能很大,因此,并不能用康托展开有效解决。

那么,整个过程分为3步:

 

  1. 将排列数变成变进制数
  2. 将变进制数加上m
  3. 将变进制数变成排列数

 

我们来看一个实例: 将 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”

 

posted @ 2022-01-24 20:19  Endergarten  阅读(89)  评论(0)    收藏  举报