zoukankan      html  css  js  c++  java
  • 已知一个排列求下一个排列(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("
    ");
    68 
    69     }
    70 
    71     return 0;
    72 }
    View Code
     
  • 相关阅读:
    Gym
    Gym
    Gym
    LA 3713 宇航员分组
    LA 3211 飞机调度(2—SAT)
    POJ 1050 To The Max
    51nod 1050 循环数组最大子段和
    UVa 11149 矩阵的幂(矩阵倍增法模板题)
    POJ 1236 Network of School
    UVa 11324 最大团(强连通分量缩点)
  • 原文地址:https://www.cnblogs.com/mhpp/p/7989975.html
Copyright © 2011-2022 走看看