牛客多校第三场 I 题题解

Ice Drinking

https://ac.nowcoder.com/acm/contest/33188/I

题意:给定 nn,记随机变量 XX 为一个长度为 nn 的排列中满足 ai=ia_i=i 的个数,求所有排列中 E(Xk)E(X^k)1n1×10181 \leq n\leq 1\times 10^{18}0kn+50000 \leq k \leq n+5000

解法:由错排定义可得 n!=i=0n(ni)Dni\displaystyle n!=\sum_{i=0}^n {n \choose i}D_{n-i}

E(Xk)=i=0nikPr[x=i]=i=0nik(ni)Dni=i=0nDni(ni)j=0kj!(ij){kj}=i=0nj=0k(ni)(ij)j!{kj}Dni=i=0nj=0k(nj)(njij)j!{kj}Dni=j=0k(nj){kj}j!(i=jn(njij)Dni)=j=0k(nj){kj}j!(i=0nj(nji)Dnij)=j=0k(nj){kj}j!(nj)!=j=0kn!j!(nj)!{kj}j!(nj)!=n!j=0n{kj}\begin{aligned} E(X^k)=&\sum_{i=0}^n i^k \Pr[x=i]\\ =&\sum_{i=0}^n i^k {n \choose i} D_{n-i}\\ =&\sum_{i=0}^n D_{n-i}{n \choose i}\sum_{j=0}^k j!{i \choose j}{k \brace j}\\ =&\sum_{i=0}^n \sum_{j=0}^k {n \choose i}{i \choose j}j!{k \brace j}D_{n-i}\\ =&\sum_{i=0}^n \sum_{j=0}^k {n \choose j}{n-j \choose i-j}j!{k \brace j}D_{n-i}\\ =&\sum_{j=0}^k {n \choose j} {k \brace j}j!\left(\sum_{i=j}^n{n-j \choose i-j} D_{n-i}\right)\\ =&\sum_{j=0}^k {n \choose j} {k \brace j}j!\left(\sum_{i=0}^{n-j}{n-j \choose i} D_{n-i-j}\right)\\ =&\sum_{j=0}^k{n \choose j}{k \brace j}j!(n-j)!\\ =&\sum_{j=0}^k\dfrac{n!}{j!(n-j)!}{k \brace j}j!(n-j)!\\ =&n!\sum_{j=0}^n {k \brace j} \end{aligned}

因而对于 nkn \geq k,答案为贝尔数 ϖn=i=0k{ki}\displaystyle \varpi_n=\sum_{i=0}^k {k \brace i};对于 n<kn<k,需要手动计算出最后几项斯特灵数,然后进行相减。

求贝尔数对 pp 取模,可以参考本文https://www.cnblogs.com/lfri/p/11546313.html:利用 ϖn+pϖn+ϖ(n+1)(modp)\varpi_{n+p}≡\varpi_n + \varpi_{(n+1)} \pmod ppp 是质数,其时间复杂度为 O(p2logn)\mathcal O(p^2 \log n)

对于斯特灵数的快速计算,对于本题 kn5×103k-n \leq 5\times 10^3,可以采用二阶欧拉数 nk\left\langle \left\langle \begin{matrix}n\\k\end{matrix} \right\rangle \right\rangle

{xxn}=k=0nnk(x+n1k2n){x \brace x-n}=\sum_{k=0}^n \left\langle \left\langle \begin{matrix}n\\k\end{matrix} \right\rangle \right\rangle {x+n-1-k \choose 2n}

其中二阶欧拉数递推式为 nk=(k+1)n1k+(2n1k)n1k1\left\langle \left\langle \begin{matrix}n\\k\end{matrix} \right\rangle \right\rangle=(k+1)\left\langle \left\langle \begin{matrix}n-1\\k\end{matrix} \right\rangle \right\rangle+(2n-1-k)\left\langle \left\langle \begin{matrix}n-1\\k-1\end{matrix} \right\rangle \right\rangle。该时间复杂度为 O((kn)2)\mathcal O((k-n)^2)

此外还有一个做法,来源于 hos.lyric

{nk}modp={(xy)modp,if pk,ykp,xnyp1(xy){n1(p1)xyk}modp,if pk,ykp,xny1p1{n \brace k} \bmod p= \begin{cases} \displaystyle {x \choose y} \bmod p,{\rm if}\ p|k,y \leftarrow \dfrac{k}{p},x \leftarrow \left \lfloor \dfrac{n-y}{p-1}\right \rfloor\\ \displaystyle {x \choose y}{n-1-(p-1)x-y \brace k} \bmod p, {\rm if}\ p \nmid k,y \leftarrow \left \lfloor \dfrac{k}{p}\right \rfloor,x \leftarrow \left \lfloor \dfrac{n-y-1}{p-1}\right \rfloor \end{cases}

具体证明可以参看http://matwbn.icm.edu.pl/ksiazki/aa/aa94/aa9413.pdf。

hos 对此的解释为:

I just guessed the pattern by looking the table for p=7p=7. We can prove that Stirling1 mod p has a nice pattern because x(x1)...(x(p1))xpx(modp)x(x-1)...(x-(p-1)) \equiv x^p-x \pmod p, and s***irling2 is its inverse matrix, my intuition says it is correct.

如果提前预处理好 p2p^2 的斯特灵数表和组合数表,利用 Lucas 定理可以 O(logn)\mathcal O(\log n) 的求出。

由于本题的模数 862118861=857×997×1009862118861=857\times 997\times 1009,因而可以通过中国剩余定理,对于每个质因子单独考虑,然后合并。最终复杂度为 O(p2logn+(kn)2)\mathcal O(p^2\log n+(k-n)^2)

#include <bits/stdc++.h>
using namespace std;
const int P = 862118861;
const int mod[3] = {857, 997, 1009};
class Bell
{
    const static int N = 1100;
    vector<vector<int>> Stirling;
    vector<int> bell;
    int mod;

public:
    Bell(int mod)
    {
        bell.resize(N + 5);
        Stirling.resize(N + 5);
        for (auto &i : Stirling)
            i.resize(N + 5);
        this->mod = mod;
        Stirling[0][0] = 1;
        for (int i = 0; i <= N; i++)
            for (int j = 1; j <= i; j++)
                Stirling[i][j] = (Stirling[i - 1][j - 1] + 1LL * j * Stirling[i - 1][j]) % mod;
        bell[0] = 1;
        for (int j = 1; j <= N; j++)
        {
            bell[j] = 0;
            for (int k = 1; k <= j; k++)
                bell[j] = (bell[j] + Stirling[j][k]) % mod;
        }
    }
    int query(long long n)
    {
        if(n < mod)
            return bell[n];
        vector<int> B(N + 5, 0), a(N + 5, 0), d(N + 5, 0);
        for (int i = 0; i <= mod;i++)
            B[i] = bell[i];
        int cnt = 0;
        while (n)
        {
            a[cnt++] = n % mod;
            n /= mod;
        }
        for (int i = 1; i < cnt; i++)
            for (int j = 1; j <= a[i]; j++)
            {
                for (int k = 0; k < mod; k++)
                    d[k] = (B[k + 1] + i * B[k] % mod) % mod;
                d[mod] = (d[0] + d[1]) % mod;
                for (int k = 0; k <= mod; k++)
                    B[k] = d[k];
            }
        return d[a[0]];
    }
};
class Binom
{
    int mod;
    const static int N = 1100;
    vector<vector<int>> C;

public:
    void set(int mod)
    {
        C.resize(N + 5);
        for (auto &i : C)
            i.resize(N + 5);
        this->mod = mod;
        for (int i = 0; i <= N;i++)
            C[i][i] = C[i][0] = 1;
        for (int i = 1; i <= N;i++)
            for (int j = 1; j < i;j++)
                C[i][j] = (C[i - 1][j - 1] + C[i - 1][j]) % mod;
    }
    long long query(long long n, long long m)
    {
        if(n < mod && m < mod)
            return C[n][m];
        else
            return C[n % mod][m % mod] * query(n / mod, m / mod) % mod;
    }
};
class Euler
{
    const static int N = 5000;
    vector<vector<int>> euler;
    int mod;
    Binom b;

public:
    Euler(int mod)
    {
        euler.resize(N + 5);
        for (auto &i : euler)
            i.resize(N + 5);
        b.set(mod);
        this->mod = mod;
        euler[0][0] = 1;
        for (int i = 1; i <= N;i++)
        {
            euler[i][0] = 1;
            euler[i][i] = 0;
        }
        for (int i = 2; i <= N;i++)
            for (int j = 1; j < i;j++)
                euler[i][j] = (euler[i - 1][j] * (j + 1) + (2 * i - 1 - j) * euler[i - 1][j - 1]) % mod;
    }
    long long query_stirling(long long x, int n)
    {
        long long ans = 0;
        for (int k = 0; k <= n;k++)
            ans = (ans + euler[n][k] * b.query(x + n - 1 - k, 2 * n) % mod) % mod;
        return ans;
    }
};
int main()
{
    long long n, m;
    scanf("%lld%lld", &n, &m);
    long long ans[3] = {0};
    for (int i = 0; i < 3;i++)
    {
        Bell solve(mod[i]);
        ans[i] = solve.query(m);
        // printf("SOLVE:%d %lld\n", mod[i], ans[i]);
    }
    if (n < m)
        for (int i = 0; i < 3;i++)
        {
            Euler solve(mod[i]);
            for (int j = 0; j < m - n;j++)
                ans[i] = (ans[i] - solve.query_stirling(m, j) + mod[i]) % mod[i];
        }
    long long res = (463753553LL * ans[0] + 376150155LL * ans[1] + 22215154LL * ans[2]) % P;
    printf("%lld", res);
    return 0;
}
全部评论

相关推荐

不愿透露姓名的神秘牛友
11-27 10:52
点赞 评论 收藏
分享
点赞 评论 收藏
分享
头像
11-27 14:28
长沙理工大学
刷算法真的是提升代码能力最快的方法吗?&nbsp;刷算法真的是提升代码能力最快的方法吗?
牛牛不会牛泪:看你想提升什么,代码能力太宽泛了,是想提升算法能力还是工程能力? 工程能力做项目找实习,算法也分数据结构算法题和深度学习之类算法
点赞 评论 收藏
分享
评论
点赞
收藏
分享
牛客网
牛客企业服务