题目描述略)
本题题意为求给定长度为 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 为长度的全排列的末排列。当然,在本题中无此类情况。
1 #include"iostream" 2 #include"stdio.h" 3 using namespace std; 4 int number[10005]; 5 int main() 6 { 7 freopen("martian.in","r",stdin); 8 freopen("martian.out","w",stdout); 9 int i,j,m,n,temporary; 10 cin>>n>>m; 11 for(i=0;i<n;i++) 12 scanf("%d",&number[i]); 13 while(m--) 14 { 15 for(i=n-2;number[i]>number[i+1];i--); 16 j=i+1; 17 for(int k=i+2;k<n;k++) 18 if((number[i]<number[k])&&(number[j]>number[k])) 19 j=k; 20 temporary=number[i]; 21 number[i]=number[j]; 22 number[j]=temporary; 23 for(int left=i+1,right=n-1;left<right;left++,right--) 24 temporary=number[left], 25 number[left]=number[right], 26 number[right]=temporary; 27 } 28 for(i=0;i<n;i++) 29 printf("%d ",number[i]); 30 return 0; 31 }
另一边博客
本题就是要求求出num[N]经过M次排列后的结果。
对于一系列的数,比如int num[5] = {1,2,3,4,5}这个数组,要想对它进行全排列,要经过以下几个步骤:
1.判断该数组能不能进行全排列
对于一个数组来说,如果他为num[5] = {5,4,3,2,1},那么也就没有必要再去全排列了,因为他已经是最大的数字了,没有后继。所以,想要判断一系列数能不能进行全排列,判断他有没有后继(即这个数是否存在非递减的两个数),如果有(存在),那就可以进行排列。
判断是否能进行全排列的代码:
bool hasNext() { for( int i = N; i > 0; i--) if( num[i] > num[i-1]) return true; return false; }
2、.如何进行全排列(当时想这个想了挺久的==)
在确定这一系列的数有后继之后,那如何去找到它的后继呢?要明确,一个数的后继要满足两个条件:比这个数大、在比这个数大的数里面最小。
首先,我们从右往左遍历这个数组,找出一个数num[i],满足num[i]>num[i-1],然后用top将这个i记录下来(即top为极大值点),并且确定了一个要交换的数num[top-1];
接着,我们要确定第二个要交换的数, 而第二个要交换的数为num[top]-num[N]中最小的数并且这个数要大于第一个被交换的数num[top-1];
然后,交换两个数;
最后,如果交换之后,num[top]及其后面的数如果还是单调递减的,那就将其位置对调,得到最小的。
找出极大值得top并记录
for( int i = N-1; i >0; i--) { if( num[i] > num[i-1]) { top = i; break; } }
确定第二个要交换的数
int mm = top;//mm为要交换数的下标 //如果top后面还有比top前面的数(也就是num[top-1)小的话,就先交换那个小的数 for( int i = top; i < N; i++) { if( num[i] > num[top-1] && num[i] < num[top]) mm = i; }
交换两个数
void _swap( int *a, int *b) { int temp = *a; *a = *b; *b = temp; } _swap(&num[mm],&num[top-1]);
得到最小
for(int i=0;i<=(top+N-1)/2-top;i++) _swap(&num[i+top],&num[N-1-i]);
大概的思路就是这个样子了==
可能讲的还不是太清楚,其实对于全排列问题,用递归、c++的库函数都可以完成的。
源码:
1 #include<cstdio> 2 3 using namespace std; 4 const int maxn = 10000+5; 5 int num[maxn]; 6 int N,M; 7 8 //个人觉得这个不写也没问题,但是为了安全,还是写着吧 9 int hasNext() 10 { 11 for( int i = N-1; i > 0; i--) 12 if( num[i] > num[i-1]) 13 return true; 14 return false; 15 } 16 17 void _swap( int *a, int *b) 18 { 19 int m = *a; 20 *a = *b; 21 *b = m; 22 } 23 24 void next() 25 { 26 int top; 27 //从又开始遍历数组,找出右边第一个极大值,用top保存(此时也找到了第一个要交换的数num[top-1]) 28 for( int i = N-1; i > 0; i--) 29 if( num[i] > num[i-1]) 30 { 31 top = i; 32 break; 33 } 34 35 //找出第二个要交换的数 36 int mm = top; 37 for( int i = top; i < N; i++) 38 { 39 if( num[i] > num[top-1] && num[i] < num[top]) 40 mm = i; 41 } 42 43 _swap( &num[top-1], &num[mm]); 44 45 for( int i = 0; i <= (top+N-1)/2-top; i++) 46 _swap( &num[i+top], &num[N-1-i]); 47 48 } 49 50 int main() 51 { 52 while( scanf("%d%d",&N,&M) == 2) 53 { 54 55 for( int i = 0; i < N; i++) 56 scanf("%d",&num[i]); 57 58 for( int i = 0; i < M; i++) 59 { 60 if( hasNext()) 61 next(); 62 } 63 64 printf("%d",num[0]); 65 for( int i = 1; i < N; i++) 66 printf(" %d",num[i]); 67 printf("\n"); 68 69 } 70 71 return 0; 72 }