已知一个排列求下一个排列(NOIP2004P4 火星人 题解)转

题目描述略)

本题题意为求给定长度为 n 的数列的后第 m 个全排列(字典序)。

对于一个给定的数列 a[0 .. n-1],求其下一个字典序的全排列算法如下:

  1. 从右向左查询最大的下标 i (0 ≤ i ≤ n-1) 使得 a[i] < a[i+1];
  2. 从左向右查询最小的元素 a[j] (i+1 ≤ j ≤ n-1) 使得 a[i] < a[j];
  3. 交换 a[i] 和 a[j];
  4. 逆置翻转 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 }
View Code

另一边博客

本题就是要求求出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 }
View Code

 

 

转载于:https://www.cnblogs.com/mhpp/p/7989975.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值