已知一个排列求下一个排列(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 }

另一边博客

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

 

时间: 2024-11-03 22:14:35

已知一个排列求下一个排列(NOIP2004P4 火星人 题解)转的相关文章

stl 之 next_permutation 求出一个排序的下一个排列的函数 转载

这是一个求一个排序的下一个排列的函数,可以遍历全排列,要包含头文件<algorithm>下面是以前的笔记    与之完全相反的函数还有prev_permutation  (1) int 类型的next_permutation int main(){ int a[3];a[0]=1;a[1]=2;a[2]=3; do{cout<<a[0]<<" "<<a[1]<<" "<<a[2]<<

已知s.txt文件中有一个这样的字符串 请编写程序读取数据内容,把数据排序后写入 ss.txt文件

package cn.idcast5; import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.FileNotFoundException; import java.io.FileReader; import java.io.FileWriter; import java.io.IOException; import java.util.Arrays; /* * 需求:已知s.txt文件中有一个这样

已知三点求圆心与半径

已知三点求圆心与半径  [email protected] http://blog.csdn.net/kezunhai 在计算机图像图形学中,经常会用到求圆心或圆半径的情况,本文介绍一种已知三个点求圆心和圆半径的方法(当然三个点不能共线,共线的三个点不能构成圆). 原理:相互连接三个点,选取其中的任意两条直线,通过对这两条直线的中心做垂线,两条垂线的交点就是圆心,以此点为圆心,以此点到任意一点的距离为半径画圆. 三个点分别计为pt1, pt2, pt3:取直线p1p2和p1p3(也可以取其他直线

PHP 获取数组任意下标key的上一个prev和下一个next下标值

PHP 获取数组任意下标key的上一个prev和下一个next下标值 <?php $xoops[1] = '小'; $xoops[2] = '孩'; $xoops[3] = '子'; $xoops[4] = '气'; $steps = new Steps(); foreach($xoops as $key=>$value){ $steps->add($key); } $steps->setCurrent(3);//参数为key值 echo '上一个下标:'.$steps->g

已知矩形面积求最小周长

Description There is a piece of paper in front of Tom, its length and width are integer. Tom knows the area of this paper, he wants to know the minimum perimeter of this paper. Input In the first line, there is an integer T indicates the number of te

poj 2002(好题 链式hash+已知正方形两点求另外两点)

Squares Time Limit: 3500MS   Memory Limit: 65536K Total Submissions: 18493   Accepted: 7124 Description A square is a 4-sided polygon whose sides have equal length and adjacent sides form 90-degree angles. It is also a polygon such that rotating abou

LeetCode OJ:Populating Next Right Pointers in Each Node II(指出每一个节点的下一个右侧节点II)

Follow up for problem "Populating Next Right Pointers in Each Node". What if the given tree could be any binary tree? Would your previous solution still work? Note: You may only use constant extra space. For example,Given the following binary tr

js获取上一个月、下一个月格式为yyyy-mm-dd的日期

/** * 获取上一个月 * * @date 格式为yyyy-mm-dd的日期,如:2014-01-25 */ function getPreMonth(date) { var arr = date.split('-'); var year = arr[0]; //获取当前日期的年份 var month = arr[1]; //获取当前日期的月份 var day = arr[2]; //获取当前日期的日 var days = new Date(year, month, 0); days = da

js计算当前日期上一个月和下一个月

/**         * 获取上一个月         *         * @date 格式为yyyy-mm-dd的日期,如:2014-01-25         */        function getPreMonth(date) {            var arr = date.split('-');            var year = arr[0]; //获取当前日期的年份            var month = arr[1]; //获取当前日期的月份