(题目描述略)
本题题意为求给定长度为 n 的数列的后第 m 个全排列(字典序)。
对于一个给定的数列 a[0 .. n-1],求其下一个字典序的全排列算法如下:
- 从右向左查询最大的下标 i (0 ≤ i ≤ n-1) 使得 a[i] < a[i+1];
- 从左向右查询最小的元素 a[j] (i+1 ≤ j ≤ n-1) 使得 a[i] < a[j];
- 交换 a[i] 和 a[j];
- 逆置翻转 a[i+1 .. n-1]。
算法分析:我们可以发现,第一步求出的 i 下标表示 a[i+1 .. n-1] 是一个长度为 n-i-1 的最后一个全排列,且 a[i .. n-1] 是一个长度为 n-i 的非末全排列。这样,我们可以不改变 a[0 .. i-1],而对 a[i .. n-1] 求其下一个全排列。
因为以 a[i] 为起始的全排列已经完成,所以其构造方法必然是将 a[i] 换成 a[i+1 .. n-1] 中比 a[i] 大的且最小的数,即为 a[j]。下面我们来比较 a[i] 和 a[j+1] 之间的大小关系。显然,a[i] ≠ a[j+1]。假设 a[i] < a[j+1],我们有 a[i] < a[j+1] < a[j],与条件 a[j] 为所有大于 a[i] 的数中最小的数矛盾。故 a[i] > a[j+1]。
由于 a[i+1] > a[i+2] > .. > a[j] > a[j+1] > .. > a[n-1],且 a[i] < a[j],a[i] > a[j+1],故 a[i+1] > a[i+2] > .. > a[j] > a[i] > a[j+1] > .. > a[n-1]。当交换 a[i] 和 a[j] 后,a[i+1 .. n-1] 必然严格降序排列。显然,交换 a[i] 和 a[j] 前 a[i .. n-1] 的下一个排列为交换 a[i] 和 a[j] 后以 a[i] 为起始的第一个排列。于是,将 a[i+1 .. n-1] 逆置翻转,得到原数列的下一个全排列。
特别的,当 i 不存在时,原数列即为以 n 为长度的全排列的末排列。当然,在本题中无此类情况。
代码如下:
#include"iostream"
#include"stdio.h"
using namespace std;
int number[10005];
int main()
{
freopen("martian.in","r",stdin);
freopen("martian.out","w",stdout);
int i,j,m,n,temporary;
cin>>n>>m;
for(i=0;i<n;i++)
scanf("%d",&number[i]);
while(m--)
{
for(i=n-2;number[i]>number[i+1];i--);
j=i+1;
for(int k=i+2;k<n;k++)
if((number[i]<number[k])&&(number[j]>number[k]))
j=k;
temporary=number[i];
number[i]=number[j];
number[j]=temporary;
for(int left=i+1,right=n-1;left<right;left++,right--)
temporary=number[left],
number[left]=number[right],
number[right]=temporary;
}
for(i=0;i<n;i++)
printf("%d ",number[i]);
return 0;
}
对于本题,我们还可以调用库函数直接出解。next_permutation 是一个定义在 algorithm 库里的函数,功能是求一个一维整型数组的下一个全排列,原理同上,用法见下。
代码如下:
#include"algorithm"
#include"iostream"
#include"stdio.h"
using namespace std;
int a[10005];
int main()
{
freopen("martian.in","r",stdin);
freopen("martian.out","w",stdout);
int m,n;
cin>>n>>m;
for(int i=0;i<n;i++)
scanf("%d",&a[i]);
while(m--)
next_permutation(a,a+n);
for(int i=0;i<n;i++)
printf("%d ",a[i]);
return 0;
}
时间复杂度:O(nm)