LG5409 第一类斯特林数·列 题解


Tag

多项式。

Preface

以后别人不会求这玩意的时候我就可以自信的说出“看我博客”这种话了……

Description

给定 \(n,k\),对于所有 \(0 \leq i \leq n\),求出 \(\left[\begin{matrix}i\\k\end{matrix}\right]\) 的值,对 \(167772161\) 取模。

\(\texttt{data range:} 1\leq n,k< 131072\).

Solution

下面令 \(s(n,k)\) 为第一类斯特林数 \(\left[\begin{matrix}i\\k\end{matrix}\right]\)。(说实话每一次打个矩阵挺扯淡的)

\(s(n,k)\) 读作"\(n\) 轮换 \(k\)",表示的是 \(n\) 个有标号节点有 \(k\) 个轮换的方案数。

具体的,我们用一个置换来描述一个排列的话,那么我们可以得到若干个“环”,根据第一类斯特林数的定义,我们可以得到如果一个长为 \(n\) 的置换具有 \(k\) 个环,其方案数就是 \(s(n,k)\)

接下来我们通过一些简单的组合意义证明 \(s(n,1)=(n-1)!,\sum\limits_is(n,i)=n!\) 这两个比较重要的结论。

我们可以将我们刚刚提到的置换具体的表示出来,比如 \((3)(41)(652)(87)\) 这样一个置换,将每一个环的最大的一个元素放到第一个的位置,然后按照第一个位置排序,然后将括号给去掉,就可以得到一个排列 \(34165287\)(注意这里并不是置换而是排列)。

不难发现我们随意改变一个值的位置就会变成另一个方案,所以此时证明了 \(\sum\limits_i s(n,i)=n!\)

\(k=1\) 的时候,一定是最大的元素 \(n\) 在最前面,然后后面 \(n-1\) 个元素随便排,所以得到了 \(s(n,1)=(n-1)!\)

回到本题。

不妨设 EGF \(F_k(z)\) 为有 \(k\) 个环的置换的个数,根据上面求得的

\[F_1(z) = \sum_n(n-1)!\dfrac{z^n}{n!}=\ln \dfrac{1}{1-z} \]

那么对于 \(F_k(z)\) 我们就很容易得到就是一个环的卷 \(k\) 次了,但是注意是无序的,所以要除去 \(k!\),我们最终的答案的 EGF 就是:

\[\dfrac{\left(\ln \dfrac 1{1-z}\right)^k}{k!} \]

注意这里是 EGF,所以答案的最后需要乘上一个 \(i!\)

对于我们导出的 \(F_1(z)\) 实际上对于 \(\sum\limits_is(n,i)=n!\) 有另一种解决方法。

不难设 EGF \(G(z)\) 表示长度为 \(n\) 的排列可以表示多少种置换,再根据上面的组合意义可以得到:

\[\exp(F_1(z))=G(z) \]

此时

\[G(z)=\sum_{n} n!\dfrac{z^n}{n!}=\dfrac 1{1-z} \]

所以 \(F_1(z) = \ln \dfrac 1{1-z}\),之后就是相同的方法了。

求解方面可以通过向左移 \(\ln \dfrac 1{1-z}\) 的方法来用多项式 exp 来解决,最后再右移回去就可以了,时间复杂度为 \(O(n\lg n)\)

但是实际上 \(O(n \lg n\lg k)\) 也过得去,甚至有时候比上面那种方法还快一点。

Code

using ll = long long;
using poly = vector;

const int N = 131072 * 2;
const int mod = 167772161, g = 3;
//const int mod = 998244353, g = 3;

inline void chk(int &x) {x -= mod; x += x >> 31 & mod;}
inline int mll(int x, int y) {return (ll) x * y % mod;}
inline int add(int x, int y) {chk(x += y); return x;}
inline int del(int x, int y) {return add(x, mod - y);}

inline int ksm(int x, int y) {
    int ret = 1;
    for(; y; y >>= 1, x = mll(x, x))
        if(y & 1) ret = mll(ret, x);
    return ret;
}

int fc[N], fv[N], inv[N];
inline void pref(const int lim) {
    fc[0] = 1;
    FOR(i, 1, lim) fc[i] = mll(fc[i - 1], i);
    fv[lim] = ksm(fc[lim], mod - 2);
    ROF(i, lim, 1) fv[i - 1] = mll(fv[i], i);
    FOR(i, 1, lim) inv[i] = mll(fv[i], fc[i - 1]);
    return ;
}

int rev[N << 1];
inline int getrev(const int n) {
    int len = 1, tim = -1;
    while(len < n) len <<= 1, tim++;
    FOR(i, 0, len - 1) rev[i] = rev[i >> 1] >> 1 | ((i & 1) << tim);
    return len;
}

void DFT(poly &F, int n) {
    F.resize(n);
    FOR(i, 0, n - 1) if(i < rev[i]) swap(F[i], F[rev[i]]);
    for(int i = 1; i < n; i <<= 1) {
        int gn = ksm(g, (mod - 1) / (i << 1));
        for(int j = 0, x, y, g0 = 1; j < n; j += (i << 1), g0 = 1)
        for(int k = 0; k < i; k++, g0 = mll(gn, g0)) {
            x = F[j + k], y = mll(F[i + j + k], g0);
            F[j + k] = add(x, y);
            F[i + j + k] = del(x, y);
        }
    }
    return ;
}

void IDFT(poly &F, int n) {
    DFT(F, n), reverse(F.begin() + 1, F.end());
    int iv = ksm(n, mod - 2);
    FOR(i, 0, n - 1) F[i] = mll(F[i], iv);
    return ;
}

poly operator + (poly A, poly B) {
    int l = max(A.size(), B.size());
    A.resize(l), B.resize(l);
    FOR(i, 0, l - 1) A[i] = add(A[i], B[i]);
    return A;
}

poly operator - (poly A, poly B) {
    int l = max(A.size(), B.size());
    A.resize(l), B.resize(l);
    FOR(i, 0, l - 1) A[i] = del(A[i], B[i]);
    return A;
}

poly operator * (poly A, int k) {
    FOR(i, 0, A.size() - 1) A[i] = mll(A[i], k);
    return A;
}

poly operator * (poly A, poly B) {
    int n = A.size() + B.size() - 1, len = getrev(n);
    DFT(A, len), DFT(B, len);
    FOR(i, 0, len - 1) A[i] = mll(A[i], B[i]);
    return IDFT(A, len), A.resize(n), A;
}

poly DI(const poly F) {
    poly G(F.size() - 1);
    FOR(i, 0, G.size() - 1) G[i] = mll(F[i + 1], i + 1);
    return G;
}

poly IG(const poly F) {
    poly G(F.size() + 1);
    FOR(i, 1, G.size() - 1) G[i] = mll(F[i - 1], inv[i]);
    return G;
}

poly INV(const poly F) {
    poly G; G.push_back(ksm(F[0], mod - 2));
    int n = F.size() << 1;
    for(int mx = 2; mx < n; mx <<= 1) {
        poly H = F; H.resize(mx);
        int len = getrev(mx << 1);
        DFT(G, len), DFT(H, len);
        FOR(i, 0, len - 1) G[i] = mll(G[i], del(2, mll(H[i], G[i])));
        IDFT(G, len), G.resize(mx);
    }
    return G.resize(F.size()), G;
}

poly LN(const poly F) {
    poly G = IG(DI(F) * INV(F));
    return G.resize(F.size()), G;
}

poly EXP(const poly F) {
    poly G; G.push_back(1);
    int n = F.size() << 1;
    for(int mx = 2; mx < n; mx <<= 1) {
        poly H = F;
        H.resize(mx), G.resize(mx);
        G = G * (poly(1, 1) - LN(G) + H);
    }
    return G.resize(F.size()), G;
}

poly Mulx(poly F, const int k) {
    poly G(F.size() + k);
    FOR(i, k, G.size() - 1) G[i] = F[i - k];
    return G;
}

poly Divx(poly F, const int k) {
    poly G(F.size() - k);
    FOR(i, 0, G.size() - 1) G[i] = F[i + k];
    return G;
}

void print(const poly F) {
    FOR(i, 0, F.size() - 1) cerr << F[i] << " \n"[i == ii];
}

inline void solve() {
    int n = rd, k = rd;
    int p = max(n, k);
    pref(p + 10);
    poly F;
    FOR(i, 0, n) F.push_back(1);
    F = LN(F);
    poly H = Divx(F, 1);
    H = EXP(LN(H) * k);
    F.clear(), F.resize(n + 1);
    FOR(i, k, n) F[i] = mll(H[i - k], fv[k]);
    FOR(i, 0, n) cout << mll(F[i], fc[i]) << ' ';
    return ;
}

Final

本来想用二元生成函数去解决的,但是运用的不是很熟练,还是只能拼来拼去的用,有没有老哥教教我怎么用二元生成函数啊。

自此,关于斯特林数的四道题全部完成了。