【洛谷 P1088】火星人

题目链接

https://www.luogu.com.cn/problem/P1088

题解

关于顺序枚举若干排列问题,大概有三种方法:

  1. C++ STL 中的 next_permutation()

  2. 手动实现 next_permutation()

  3. 康托展开

此处采用第二种方法。

我们考虑要增大一个排列的方法是将一个顺序对变为逆序对;

对于某个排列,其最后几个元素构成一个下降序列(长度最小为 \(1\));

要将序列在全排列中的顺序增加 \(1\),每次交换的两对数应当尽量靠后,

假设序列为 \(a_1, a_2, ..., a_n\),记该下降序列起始元素的位置为 \(i\)

记该下降序列中最靠后的比 \(a_i\) 的元素为 \(a_j\)

则应交换 \(a_i\)\(a_j\)

交换过后,还应将从 \(i\)\(n\) 的序列逆序,即将其转换为上升序列;

经过数学归纳法证明,不断重复此方法能得到给定长度的全排列顺序枚举。

AC 代码

点击展开
#include <stdio.h>
#define MAXN 10005

void swap(int *a, int *b) {
    *a ^= *b;
    *b ^= *a;
    *a ^= *b;
}

int nextPermutation(int *a, const int n) {
    int p = 0;
    for (int i = n - 1; i >= 1; --i)
        if (a[i] < a[i + 1]) {
            p = i;
            break;
        }
    for (int i = n; i > p; --i)
        if (a[i] > a[p]) {
            swap(&a[i], &a[p]);
            break;
        }
    for (int i = p + 1; i <= (n + p + 1) / 2; ++i)
        if (i != n + p + 1 - i)
            swap(&a[i], &a[n + p + 1 - i]);
    return p;
}

int main() {
    int n, m;
    static int a[MAXN];
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= n; ++i) scanf("%d", &a[i]);
    for (int i = 1; i <= m; ++i) nextPermutation(a, n);
    for (int i = 1; i <= n; ++i) printf("%d ", a[i]);
    return 0;
}

posted @ 2020-12-24 15:54  可爱的小凯凯  阅读(92)  评论(0)    收藏  举报