[NOIP2004-普及] 火星人

GitHub跳转原题关系图返回列表

排列按字典序对应整数,加 M 就是做 M 次 next_permutation:找枢轴、换最小可增大者、反转降序后缀,单步 O(N),总复杂度 O(NM)。

OJ: roj

题目 ID: 3531

难度:普及

标签:模拟排列next_permutation

创建: 2026-10-02 05:39

更新: 2026-10-06 13:16

题目描述

1∼N1 \sim N 的排列按字典序对应 1∼N!1 \sim N!,给定 NN、MM(M≤100M \le 100)与当前排列,输出对应整数加 MM 后的排列(N≤10000N \le 10000)。

思路

把排列看作康托展开的序号,加上 MM 后逆康托展开还原;因 MM 很小,等价于从排列尾部做 MM 次"下一个排列",用栈/逆序对调整即可 O(N+M⋅均摊)O(N+M \cdot \text{均摊})。

参考代码

Python 版:

python
#!/usr/bin/env python3
# Author by Rainboy blog: https://rainboylv.com github: https://github.com/rainboylvx
# rbook: -> https://rbook.roj.ac.cn  https://rbook2.roj.ac.cn
# rainboy的学习导航网站: https://idx.roj.ac.cn
# create_at: 2026-10-02 05:39
# update_at: 2026-10-02 05:39

import sys


def next_permutation(a: list[int]) -> None:
    """原地求字典序的下一个排列:把排列当作数,做一次 +1。

    从右往左找第一个小于右邻的位置 i(其后缀是降序、已是后缀最大),
    再从右往左找第一个大于 a[i] 的 j 交换,最后把降序后缀反转成升序最小。
    """
    i = len(a) - 2
    while i >= 0 and a[i] >= a[i + 1]:
        i -= 1
    if i < 0:  # 已是全局最大排列;题面保证不会出现,兜底不动
        return
    j = len(a) - 1
    while a[j] <= a[i]:
        j -= 1
    a[i], a[j] = a[j], a[i]
    a[i + 1:] = a[:i:-1]  # 交换后后缀仍降序,反转即得最小后缀


def solve() -> None:
    data = iter(map(int, sys.stdin.buffer.read().split()))
    n = next(data)
    m = next(data)  # 要加的小整数,相当于做 m 次"后继"
    a = [next(data) for _ in range(n)]

    for _ in range(m):  # M ≤ 100,逐次 +1 总计 O(N·M)
        next_permutation(a)

    print(' '.join(map(str, a)))


if __name__ == "__main__":
    solve()

C++ 版(同一算法):

cpp
/**
 * Author by Rainboy blog: https://rainboylv.com github: https://github.com/rainboylvx
 * rbook: -> https://rbook.roj.ac.cn  https://rbook2.roj.ac.cn
 * rainboy的学习导航网站: https://idx.roj.ac.cn
 * create_at: 2026-10-06 13:11
 * update_at: 2026-10-06 13:11
 */
#include <iostream>
#include <algorithm>
using namespace std;

typedef long long ll;

const int MAXN = 10005;

int n, m;
int a[MAXN]; // 当前排列

// 原地求字典序下一个排列,O(n)
void next_permutation() {
    int i = n - 2;
    while (i >= 0 && a[i] >= a[i + 1]) i--; // 从右找第一个 a[i] < a[i+1]
    if (i < 0) return; // 已是最大排列,题面保证不会出现
    int j = n - 1;
    while (a[j] <= a[i]) j--; // 从右找第一个大于 a[i] 的数
    swap(a[i], a[j]);         // 交换,让第 i 位增大得尽量少
    // 反转降序后缀 a[i+1..n-1] 为升序最小
    int l = i + 1, r = n - 1;
    while (l < r) {
        swap(a[l], a[r]);
        l++;
        r--;
    }
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    cin >> n >> m;
    for (int i = 0; i < n; i++) cin >> a[i];
    for (int i = 0; i < m; i++) next_permutation(); // 做 m 次后继
    for (int i = 0; i < n; i++) {
        if (i) cout << ' ';
        cout << a[i];
    }
    cout << '\n';
    return 0;
}