8.14.12 ACM-ICPC 组合数学:Eulerian 数
引言
在组合数学中,Eulerian 数(Eulerian Number)是一类特殊的数字序列,用于描述特定类型的排列。Eulerian 数在许多组合问题中起到重要作用,并且在 ACM-ICPC 编程竞赛中也经常出现。本文将介绍 Eulerian 数的定义、性质、递推关系及其应用。
Eulerian 数的定义
Eulerian 数 A(n,k)A(n, k)A(n,k) 表示在 nnn 个元素的所有排列中,恰好有 kkk 个位置,前一个元素比后一个元素大的排列数目。换句话说,Eulerian 数 A(n,k)A(n, k)A(n,k) 是有 kkk 个“下降”(descents)的 nnn 元排列的个数。
初值
递推关系
Eulerian 数的递推关系如下:
Eulerian 数的性质
-
对称性:
-
边界条件:
这些性质有助于简化计算和编程实现。
计算 Eulerian 数的算法
动态规划实现
为了有效地计算 Eulerian 数,我们可以采用动态规划的方法。以下是一个简单的动态规划实现:
#include <iostream>
#include <vector>
using namespace std;
// 计算 Eulerian 数
int calculateEulerianNumber(int n, int k) {
// 初始化动态规划表
vector<vector<int>> dp(n + 1, vector<int>(n, 0));
// 边界条件
dp[0][0] = 1;
// 递归关系
for (int i = 1; i <= n; ++i) {
for (int j = 0; j < i; ++j) {
dp[i][j] = (j + 1) * dp[i - 1][j] + (i - j) * dp[i - 1][j - 1];
}
}
return dp[n][k];
}
int main() {
int n = 5;
int k = 3;
cout << "Eulerian 数字 A(" << n << ", " << k << ") = " << calculateEulerianNumber(n, k) << endl;
return 0;
}
示例输出
编译并运行上述代码,将输出 Eulerian 数字 A(5,3)A(5, 3)A(5,3) 的值:
Eulerian 数字 A(5, 3) = 35
解释
- 初始化动态规划表:创建一个大小为 (n+1)×n(n + 1) \times n(n+1)×n 的二维数组
dp
,并将所有元素初始化为 0。 - 设置边界条件:根据 Eulerian 数的定义,
dp[0][0]
设置为 1。 - 填充动态规划表:使用递推关系
dp[i][j] = (j + 1) * dp[i - 1][j] + (i - j) * dp[i - 1][j - 1]
填充表格。 - 返回结果:最终结果保存在
dp[n][k]
中。
Eulerian 数的应用
在 ACM-ICPC 以及其他编程竞赛中,Eulerian 数常用于解决与排列和计数相关的问题。例如,排列中的下降数目、特定序列生成问题等。理解和掌握 Eulerian 数的计算方法,可以大大提高我们在解决这些问题时的效率和准确性。
结论
Eulerian 数在组合数学中有着重要的地位,其递推关系和性质使得计算和应用变得相对简单。通过动态规划方法,我们可以高效地计算 Eulerian 数,并在各种编程竞赛中应用这些知识来解决复杂的排列和计数问题。希望这篇博客能帮助你更好地理解和应用 Eulerian 数,为你的竞赛准备增添一份力量。
Eulerian 数的定义
Eulerian 数 A(n,m)A(n, m)A(n,m) 表示从 1 到 n 中恰好有 mmm 个元素大于前一个元素(即有 mmm 个“上升”)的排列数目。定义为:
例子
例如,从数字 1 到 3 一共有 4 种排列使得恰好有一个元素比前面的数字大:
排列 | 满足要求的排列 | 个数 |
---|---|---|
1 2 3 | 1, 2 & 2, 3 | 2 |
1 3 2 | 1, 3 | 1 |
2 1 3 | 1, 3 | 1 |
2 3 1 | 2, 3 | 1 |
3 1 2 | 1, 2 | 1 |
3 2 1 | 0 |
所以按照 A(n,m)A(n, m)A(n,m) 定义:如果 n=3n = 3n=3,m=1m = 1m=1,Eulerian 数值为 4,表示共有 4 个有 1 个元素大于前一个元素的排列。
初值
递推关系
Eulerian 数的递推关系如下:
计算 Eulerian 数的算法
C++ 实现
下面是用 C++ 实现 Eulerian 数的代码。通过递归的方法,我们可以计算 Eulerian 数。
#include <iostream>
#include <vector>
using namespace std;
// 计算 Eulerian 数
int eulerianNumber(int n, int m) {
if (m >= n || n == 0) return 0;
if (m == 0) return 1;
return ((n - m) * eulerianNumber(n - 1, m - 1)) + ((m + 1) * eulerianNumber(n - 1, m));
}
int main() {
int n = 5;
int m = 3;
cout << "Eulerian 数字 A(" << n << ", " << m << ") = " << eulerianNumber(n, m) << endl;
return 0;
}
Python 实现
以下是 Python 的实现代码:
def eulerianNumber(n, m):
if m >= n or n == 0:
return 0
if m == 0:
return 1
return ((n - m) * eulerianNumber(n - 1, m - 1)) + ((m + 1) * eulerianNumber(n - 1, m))
# 示例
n = 5
m = 3
print(f"Eulerian 数字 A({n}, {m}) = {eulerianNumber(n, m)}")
示例输出
编译并运行上述代码,将输出 Eulerian 数字 A(5,3)A(5, 3)A(5,3) 的值:
Eulerian 数字 A(5, 3) = 35
解释
- 初值处理:当 m≥nm \ge nm≥n 或 n=0n = 0n=0 时,没有满足条件的排列,即此时 Eulerian 数为 0。当 m=0m = 0m=0 时,只有降序的排列满足条件,即此时 Eulerian 数为 1。
- 递推关系:通过递推公式计算 Eulerian 数,利用较小规模问题的解来推导较大规模问题的解。
习题
以下是一些与 Eulerian 数相关的习题,供读者练习:
- CF1349F1 Slime and Sequences (Easy Version)
- CF1349F2 Slime and Sequences (Hard Version)
- UOJ 593. 新年的军队
- P7511 三到六
结论
Eulerian 数在组合数学中有着重要的地位,其递推关系和性质使得计算和应用变得相对简单。通过递归方法,我们可以高效地计算 Eulerian 数,并在各种编程竞赛中应用这些知识来解决复杂的排列和计数问题。希望这篇博客能帮助你更好地理解和应用 Eulerian 数,为你的竞赛准备增添一份力量