P4609 [FJOI2016] 建筑师(斯特林轮换数)、P3904 三只小猪(斯特林子集数)、P6620 [省选联考 2020 A 卷] 组合数问题(斯特林反演)

19 篇文章 0 订阅

斯特林数

斯特林数(Stirling numbers)在组合数学中是一类重要的数列,主要涉及将一组元素以特定方式划分的问题。斯特林数分为两类:

  1. 第一类斯特林数(斯特林轮换数)(Signed Stirling numbers of the first kind),记为 s(n, k) 或[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。

  2. 第二类斯特林数(斯特林子集数)(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。

  3. 斯特林反演(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;
}

代码思路

  1. 首先定义了常量 P 作为取模运算的模数。

  2. 在 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 取模的结果。
  3. 在 main 函数中:

    • 定义了两个二维向量 S 和 C ,并指定了它们的大小。
    • 调用 init 函数对这两个向量进行初始化。
    • 读入测试用例的数量 T 。
    • 在一个循环中,每次读入 na 和 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;
}

代码思路

  1. 数据结构

    • S 是一个三维 vector,用于存储计算过程中的数值。每个 S[x][y] 是一个长度为 100 的 vector<int> ,用于表示特定位置 (x, y) 的数字的各个位。
    • L 是一个二维 vector,用于记录每个位置 (x, y) 数字的实际长度。
  2. add 函数

    • 首先通过比较 L[x - 1][y - 1] 和 L[x - 1][y] ,确定当前位置 (x, y) 数字的初始长度。
    • 然后通过一个循环,从低位到高位对数字进行累加。累加的来源是 S[x - 1][y - 1][i] 和 y * S[x - 1][y][i] 。
    • 在累加过程中进行进位处理,将当前位的进位加到高位。
    • 最后通过一个循环,处理可能存在的超出初始长度的高位数字。
  3. 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;
}

代码思路

  1. 首先,从输入中获取了一些整数 nxP 和 m,以及一个数组 a 的值。
  2. 定义了一个快速幂函数 qpow 用于计算幂运算的结果并取模。
  3. 使用向量来替代原来的数组:
    • S 用于存储一些计算中间结果。
    • J 用于存储另一些与计算相关的中间值。
    • pw 和 pw1 分别用于存储幂运算的结果。
  4. 通过两个嵌套的循环计算 S 中的值。
  5. 同样通过循环计算 Jpw 和 pw1 的值。
  6. 最后,通过多重循环计算最终的结果 ans 并输出。
  • 19
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

筱姌

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值