📜  计算前 N 个自然数的单峰和非单峰排列(1)

📅  最后修改于: 2023-12-03 15:12:01.831000             🧑  作者: Mango

计算前 N 个自然数的单峰和非单峰排列

简介

本文将介绍计算前 N 个自然数的单峰和非单峰排列的方法。所谓单峰排列是指一个序列,其中有且仅有一个峰值(即一个极大值),而非单峰排列则没有这个限制。

算法
单峰排列

我们可以用动态规划的方法来计算前 N 个自然数的单峰排列。设 $dp[i][j]$ 表示选出 $i$ 个数,以 $j$ 结尾的单峰排列的个数。状态转移方程如下:

$$dp[i][j]=\sum_{k=1}^{j-1}dp[i-1][k]$$

其中 $k$ 从 $1$ 到 $j-1$ 枚举峰值的位置,而在峰值左边和右边选出的数必须按升序排列。因此状态转移方程的意义是,选出剩余 $i-1$ 个数,以 $k$ 结尾的单峰排列的数量,再将第 $i$ 个数插入到 $k$ 左边或右边。

边界条件为,$dp[0][j]=1$,$dp[i][j]=0(i>j)$,因为不能选出比已选中的数还小的数。

最终的答案就是 $\sum_{j=1}^N dp[N][j]$。

时间复杂度为 $O(N^3)$。

下面是 C++ 实现的代码片段:

int dp[N + 1][N + 1];
memset(dp, 0, sizeof(dp));
for (int j = 1; j <= N; j++) dp[0][j] = 1;
for (int i = 1; i <= N; i++) {
    for (int j = 1; j <= N; j++) {
        for (int k = 1; k < j; k++) {
            dp[i][j] += dp[i - 1][k];
        }
    }
}
int ans = 0;
for (int j = 1; j <= N; j++) ans += dp[N][j];
非单峰排列

对于计算前 N 个自然数的非单峰排列,可以用康托展开的方法。康托展开是一个将排列转换为一个唯一的自然数的方法。具体方法可以参考这里。我们可以枚举峰值位置和峰值的高度,再使用康托展开的方法将前半段和后半段都按升序排列的排列数相乘,即可得到所有非单峰排列的数量。

时间复杂度为 $O(N^3\log N)$。

下面是 C++ 实现的代码片段:

// 计算康托展开的函数 fac(x) 表示 x 的阶乘
int fac(int x) {
    int ans = 1;
    for (int i = 1; i <= x; i++) ans *= i;
    return ans;
}
int cnt = 0;
for (int i = 2; i <= N - 2; i++) {
    for (int j = 1; j < i; j++) {
        for (int h = 1; h <= j; h++) {
            int a = i - h;
            int b = j - h;
            int c = N - i - (j - 1);
            int d = N - j - h + 1;
            cnt += fac(a + b + 1) / fac(a) / fac(b + 1) * fac(c + d + 1) / fac(c) / fac(d + 1); // 按公式计算
        }
    }
}
int ans = fac(N) - cnt; // 总排列数减去非单峰排列即为单峰排列
总结

本文介绍了计算前 N 个自然数的单峰和非单峰排列的方法,单峰排列使用了动态规划,时间复杂度为 $O(N^3)$,而非单峰排列使用了康托展开,时间复杂度为 $O(N^3\log N)$。