【洛谷 P1088】火星人
题目链接
https://www.luogu.com.cn/problem/P1088
题解
关于顺序枚举若干排列问题,大概有三种方法:
-
C++ STL 中的 next_permutation()
-
手动实现 next_permutation()
-
康托展开
此处采用第二种方法。
我们考虑要增大一个排列的方法是将一个顺序对变为逆序对;
对于某个排列,其最后几个元素构成一个下降序列(长度最小为 \(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;
}

浙公网安备 33010602011771号