首页 > 技术文章 > 【数学】斯特林子集数 | 斯特林轮换数 | 欧拉数

purinliang 2021-01-30 15:13 原文

斯特林子集数/第二类斯特林数

从把n个不同的小球放到k个相同的盒子里,且每个盒子至少要有一个小球的选法。

快速计算一行斯特林数:

使用这个式子

\[{n \brace m} = \frac{1}{m!} \sum\limits_{i=0}^m (-1)^i \binom {m}{i} (m-i)^n \]

把上式变形

\[ {n \brace m} = \frac{1}{m!} \sum\limits_{i=0}^m (-1)^i \frac{m!}{(m-i)!i!} (m-i)^n \\ =\sum\limits_{i=0}^m \frac{(-1)^i}{i!} \frac{(m-i)^n}{(m-i)!} \]

是一个卷积的形式

\[A(x)=\sum\limits_{i=0}^m \frac{(-1)^i}{i!} x^i\\ B(x)=\sum\limits_{i=0}^m \frac{i^n}{i!} x^i \]

那么

\[{n \brace m} = [x^m]A(x)B(x) \]

https://www.luogu.com.cn/problem/P5395

vector<int> A, B;
A.clear(), B.clear();
A.eb(1), B.eb(0);
for(int i = 1, fac = 1; i <= n; ++i) {
    fac = 1LL * fac * i % MOD;
    A.eb(1LL * qpow(MOD - 1, i)*qpow(fac, MOD - 2) % MOD);
    B.eb(1LL * qpow(i, n)*qpow(fac, MOD - 2) % MOD);
}
FNTT::Convolution(A, B);
A.resize(n + 1);

把n个元素分为k个非空子集的选法。

斯特林轮换数/第一类斯特林数

从把n个不同的小球放到k个相同的转盘上,且每个转盘至少要有一个小球,转盘在旋转直接相同视为同一种方案的选法。

把n个元素分为k个非空轮换的选法。

快速计算一行斯特林轮换数:

设生成函数 \(E_n(x)=\sum\limits_{i=0}^n {n \brack i}x^i\) ,同时可以写作 \(E_n(x)=\prod\limits_{i=0}^{n-1} (x+i)\)

这个式子直接分治FNTT O(nlog^2n)

for(int i = 1; i <= n; ++i) {
    vec[i].clear();
    vec[i].eb(i - 1);
    vec[i].eb(1);
}
DCFNTT(1, n);
pr(vec[1]);

(常数问题自己想办法解决)。

\(O(n\log n)\) 做法

\(E_n(x)=\prod\limits_{i=1}^{n} (x+i-1)\)

当n为奇数时: \(E_n(x)=\prod\limits_{i=1}^{n} (x+i-1)=(x+n-1)E_{n-1}(x)\)
当n为偶数时: \(E_n(x)=\prod\limits_{i=1}^{\frac{n}{2}} (x+i-1)\prod\limits_{i=\frac{n}{2}+1}^{n}(x+i-1)=\prod\limits_{i=1}^{\frac{n}{2}} (x+i-1)\prod\limits_{i=1}^{\frac{n}{2}} (x+i-1+\frac{n}{2})=E_{\frac{n}{2}}(x)E_{\frac{n}{2}}(x+\frac{n}{2})\)

问题转化为,已知形式幂级数 \(f(x)=\sum\limits_{i=0}^n a_ix^i\) ,求 \(g(x)=f(x+c)\)

\[g(x)=f(x+c)=\sum\limits_{i=0}^n a_i(x+c)^i=\sum\limits_{i=0}^n \sum\limits_{j=0}^i a_i\binom{i}{j}x^jc^{i-j} \]

\[g(x)=\sum\limits_{j=0}^n \sum\limits_{i=j}^n a_i\binom{i}{j}x^jc^{i-j} \]

\[g(x)=\sum\limits_{j=0}^n \frac{x^j}{j!} \sum\limits_{i=j}^n i!\cdot a_i\frac{c^{i-j}}{(i-j)!} \]

\[g(x)=\sum\limits_{i=0}^n \frac{x^i}{i!} \sum\limits_{j=i}^n j!\cdot a_j\frac{c^{j-i}}{(j-i)!} \]

\(A(i)=i!\cdot a_i,B(i)=\frac{c^{i}}{i!}\)

\[g(x)=\sum\limits_{i=0}^n \frac{x^i}{i!} \sum\limits_{j=i}^n A(j)B(j-i) \]

\[g(x)=\sum\limits_{i=0}^n \frac{x^i}{i!} \sum\limits_{j=0}^{n-i} A(i+j)B(j) \]

发现A和B中,j都是+1次,那么翻转其中一边就变成卷积。记 \(A(i+j)=A(n-i-j)\)

\[g(x)=\sum\limits_{i=0}^n \frac{x^i}{i!} \sum\limits_{j=0}^{n-i} A'(n-i-j)B(j) \]

\(C(i)=\sum\limits_{j=0}^{i} A'(i-j)B(j)\) ,这个就是一个卷积。
\(g(x)=\sum\limits_{i=0}^n \frac{x^i}{i!} C(n-i)\) 再卷积一次。

vector<int> Calc(int n) {
    if(n == 1) {
        vector<int> f;
        f.eb(0), f.eb(1);
        return f;
    }
    if(n % 2 == 1) {
        vector<int> f = Calc(n - 1);
        f.eb(0);
        for(int i = n; i >= 1; --i)
            f[i] = (f[i - 1] + 1LL * f[i] * (n - 1)) % MOD;
        f[0] = 1LL * f[0] * (n - 1) % MOD;
        return f;
    } else {
        int m = n / 2;
        vector<int> f = Calc(m), g(m + 1), a(m + 1), b(m + 1);
        for(int i = 0, res = 1; i <= m; ++i) {
            a[i] = 1LL * f[i] * fac[i] % MOD;
            b[i] = 1LL * res * invfac[i] % MOD;
            res = 1LL * res * m % MOD;
        }
        reverse(a.begin(), a.end());
        FNTT::Convolution(a, b);
        for(int i = 0; i <= m; ++i)
            g[i] = 1LL * invfac[i] * a[m - i] % MOD;
        FNTT::Convolution(f, g);
        f.resize(n + 1);
        return f;
    }
}

欧拉数

把n个元素排成一个序列,相邻两个标上符号,总共n-1个符号,恰好有k个小于号的选法。

https://www.luogu.com.cn/problem/P5825

推荐阅读