斯特林数
斯特林数(Stirling numbers)在组合数学中是一类重要的数列,主要涉及将一组元素以特定方式划分的问题。斯特林数分为两类:
-
第一类斯特林数(斯特林轮换数)(Signed Stirling numbers of the first kind),记为 s(n, k) 或
,代表的是将 n 个不同元素排列成 k个循环(或圈排列)的方法数目。这些循环之间是没有顺序的。当 n 和 k 都是正整数时,s(n, k)可以递归地定义为:
s(n, k) = s(n-1, k-1) - (n-1)s(n-1, k)并且有边界条件 s(n,0)=0 当 n>0,s(0, 0) = 1。
-
第二类斯特林数(斯特林子集数)(Unsigned Stirling numbers of the second kind),记为 S(n, k))或
,表示的是将 n 个不同元素划分到 k 个非空集合中的方法数目。当 n 和 k 都是正整数时,S(n, k)可以递归地定义为: S(n, k) = S(n-1, k-1) + kS(n-1, k)并且有边界条件 S(n, 0) = 0当 n > 0,S(0, 0) = 1。
-
斯特林反演(Stirling inversion)是组合数学中一种利用斯特林数进行函数或序列转换的技术。它建立在第一类斯特林数(s(n, k))和第二类斯特林数(S(n, k))之间的互逆关系上。斯特林反演公式可以用来从一个关于第二类斯特林数的表达式中推导出另一个关于第一类斯特林数的表达式,反之亦然。
P4609 [FJOI2016] 建筑师(斯特林轮换数)
题目描述
P4609 [FJOI2016] 建筑师 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
运行代码
#include <iostream>
#include <vector>
using namespace std;
const int P = 1e9 + 7;
void init(vector<vector<long long>>& S, vector<vector<long long>>& C) {
S[0][0] = 1;
for (int i = 1; i < S.size(); ++i) {
for (int j = 1; j < S[0].size(); ++j) {
S[i][j] = (S[i - 1][j - 1] + (i - 1) * S[i - 1][j]) % P;
}
}
for (int i = 0; i < C.size(); ++i) {
C[i][0] = 1;
}
for (int i = 1; i < C.size(); ++i) {
for (int j = 1; j <= i; ++j) {
C[i][j] = (C[i - 1][j - 1] + C[i - 1][j]) % P;
}
}
}
int main() {
int N = 50010, M = 210;
vector<vector<long long>> S(N, vector<long long>(M, 0));
vector<vector<long long>> C(M, vector<long long>(M, 0));
init(S, C);
int T, n, a, b, ans;
cin >> T;
while (T--) {
cin >> n >> a >> b;
ans = S[n - 1][a + b - 2] * C[a + b - 2][a - 1] % P;
cout << ans << endl;
}
return 0;
}
代码思路
-
首先定义了常量
P
作为取模运算的模数。 -
在
init
函数中:- 对二维向量
S
进行初始化,S[0][0]
初始化为 1。然后通过两个嵌套的循环计算S[i][j]
的值,其值为S[i - 1][j - 1] + (i - 1) * S[i - 1][j]
对P
取模的结果。 - 对二维向量
C
进行初始化,先将C[i][0]
都初始化为 1。然后通过两个嵌套的循环计算C[i][j]
的值,其值为C[i - 1][j - 1] + C[i - 1][j]
对P
取模的结果。
- 对二维向量
-
在
main
函数中:- 定义了两个二维向量
S
和C
,并指定了它们的大小。 - 调用
init
函数对这两个向量进行初始化。 - 读入测试用例的数量
T
。 - 在一个循环中,每次读入
n
、a
和b
的值。 - 计算
ans
的值,其为S[n - 1][a + b - 2]
与C[a + b - 2][a - 1]
的乘积对P
取模的结果。 - 输出
ans
的值。
- 定义了两个二维向量
P3904 三只小猪(斯特林子集数)
题目描述
P3904 三只小猪 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
运行代码
#include <iostream>
#include <vector>
using namespace std;
const int N = 55;
vector<vector<vector<int>>> S(N, vector<vector<int>>(N, vector<int>(100, 0)));
vector<vector<int>> L(N, vector<int>(N, 0));
void add(int x, int y) {
L[x][y] = max(L[x - 1][y - 1], L[x - 1][y]);
for (int i = 0; i < L[x][y]; i++) {
S[x][y][i] += S[x - 1][y - 1][i] + y * S[x - 1][y][i];
S[x][y][i + 1] += S[x][y][i] / 10;
S[x][y][i] %= 10;
}
while (S[x][y][L[x][y]]) {
S[x][y][L[x][y] + 1] += S[x][y][L[x][y]] / 10;
S[x][y][L[x][y]] %= 10;
L[x][y]++;
}
}
int main() {
int n, m;
cin >> n >> m;
S[0][0][0] = 1;
L[0][0] = 1;
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= i; j++) {
add(i, j);
}
}
if (!L[n][m]) cout << 0;
for (int i = L[n][m] - 1; i >= 0; i--) {
cout << S[n][m][i];
}
return 0;
}
代码思路
-
数据结构
S
是一个三维vector
,用于存储计算过程中的数值。每个S[x][y]
是一个长度为 100 的vector<int>
,用于表示特定位置(x, y)
的数字的各个位。L
是一个二维vector
,用于记录每个位置(x, y)
数字的实际长度。
-
add
函数- 首先通过比较
L[x - 1][y - 1]
和L[x - 1][y]
,确定当前位置(x, y)
数字的初始长度。 - 然后通过一个循环,从低位到高位对数字进行累加。累加的来源是
S[x - 1][y - 1][i]
和y * S[x - 1][y][i]
。 - 在累加过程中进行进位处理,将当前位的进位加到高位。
- 最后通过一个循环,处理可能存在的超出初始长度的高位数字。
- 首先通过比较
-
main
函数- 读取输入的
n
和m
,表示计算的范围。 - 初始化
S[0][0][0]
为 1,L[0][0]
为 1 ,作为计算的起点。 - 通过两层循环对
(x, y)
位置进行计算,调用add
函数进行累加和处理。 - 根据
L[n][m]
是否为 0 来判断结果是否为 0 。如果不为 0 ,则从高位到低位输出S[n][m]
的各个位。
- 读取输入的
P6620 [省选联考 2020 A 卷] 组合数问题(斯特林反演)
题目描述
P6620 [省选联考 2020 A 卷] 组合数问题 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
运行代码
#include <iostream>
#include <vector>
using namespace std;
#define N 1006
int n, x, P, m;
int qpow(int a, int b) {
int res = 1;
while (b) {
if (b & 1) res = 1LL * res * a % P;
a = 1LL * a * a % P;
b >>= 1;
}
return res;
}
int main() {
cin >> n >> x >> P >> m;
vector<int> a(m + 1);
for (int i = 0; i <= m; ++i) cin >> a[i];
vector<vector<int>> S(m + 1, vector<int>(m + 1));
S[0][0] = 1;
for (int i = 1; i <= m; ++i) {
for (int j = 1; j <= i; ++j) {
S[i][j] = (S[i - 1][j - 1] + 1LL * j * S[i - 1][j] % P) % P;
}
}
vector<int> J(m + 1);
J[0] = 1;
for (int i = 1; i <= m; ++i) {
J[i] = 1LL * J[i - 1] * (n - i + 1) % P;
}
vector<int> pw(m + 1), pw1(m + 1);
for (int i = 0; i <= m; ++i) {
pw[i] = qpow(x, i);
pw1[i] = qpow(x + 1, n - i);
}
int ans = 0;
for (int i = 0; i <= m; ++i) {
int sum = 0;
for (int j = 0; j <= i; ++j) {
sum = (sum + 1LL * S[i][j] * J[j] % P * pw[j] % P * pw1[j]) % P;
}
ans = (ans + 1LL * a[i] * sum) % P;
}
cout << ans << endl;
return 0;
}
代码思路
- 首先,从输入中获取了一些整数
n
、x
、P
和m
,以及一个数组a
的值。 - 定义了一个快速幂函数
qpow
用于计算幂运算的结果并取模。 - 使用向量来替代原来的数组:
S
用于存储一些计算中间结果。J
用于存储另一些与计算相关的中间值。pw
和pw1
分别用于存储幂运算的结果。
- 通过两个嵌套的循环计算
S
中的值。 - 同样通过循环计算
J
、pw
和pw1
的值。 - 最后,通过多重循环计算最终的结果
ans
并输出。