zoukankan      html  css  js  c++  java
  • 自然数幂和——第一类Stirling数和第二类Stirling数

    第一类Stirling数

    首先设

    $$S_k(n)=sum_{i=0}^ni^k$$

    根据第一类斯特林数的定义(P是排列数,C是组合数,s是Stirling)

    $$C_n^k={P_n^kover k!}={sum_{i=0}^k(-1)^{i+k}s(k,i)n^iover k!}$$

    变形得

    $$ n^k ={sum_{i=0}^{k-1}(-1)^{i+k}s(k,i)n^i}-k! C_n^k$$

    $n$ 从1取到n累加,

    $$S_k(n)=sum_{j=0}^n(k!C_j^k-sum_{i=0}^{k-1}(-1)^{i+k}s(k,i)j^i)$$

    拆括号

    $$=k!sum_{j=0}^nC_j^k-sum_{i=0}^{k-1}(-1)^{i+k}s(k,i)sum_{j=0}^nj^i$$

    因为 $ C_{m+1}^{n+1}=C_m^n+C_{m}^{n+1} $,可推出 $sum_{i=0}^nC_i^k=C_{n+1}^{k+1}$,

    在转换为用排列数的

    $$S_n(k)={P_{n+1}^{k+1}over k+1}-sum_{i=0}^{k-1}(-1)^{i+k}s(k,i)S_i(n)$$

    那么我们只需要用 $O(k^2)$ 地预处理出第一类斯特林数,然后按k来递推了,边界是 $S_1(n)=n(n+1)/2$

    主要运用了第一类斯特林数与排列式P的关系。

    优点是可以避开除法,不用考虑模数有没有逆元,排列数的形式一定可以整除($k+1$ 个连续值相乘,其中肯定有 $k+1$ 的倍数)

    //单次查询是 $O(k^2)$,多组测试就gg了,不知道有什么好的实现方法

    #include<bits/stdc++.h>
    using namespace std;
    
    typedef long long ll;
    const ll mod = 1e9 + 7;
    const int maxk = 2000 + 1;
    ll n, k;
    
    ll stir[maxk][maxk];
    void init()
    {
        stir[0][0] = stir[1][1] = 1;
        for(int i = 2;i < maxk;i++)
            for(int j = 1;j <= i;j++)
                stir[i][j] = (stir[i-1][j-1] + (i-1)*stir[i-1][j]) % mod;
    }
    
    ll S[maxk];     //S[i]表示前n项的i次方之和
    ll cal()
    {
        n %= mod;
        S[1] = (n+1) * n / 2 % mod;  //假设 k>=1
        for(int i = 2;i <= k;i++)
        {
            //计算前面一坨
            ll prod;
            if(i > n)  prod = 0;
            else
            {
                ll kk = i+1;
                prod = 1;
                for(ll j = 0;j <= i;j++)
                {
                    ll tmp = n+1-j;
                    if(tmp % (i+1) == 0)  prod = prod * (tmp/(i+1)) % mod;
                    else prod = prod * tmp % mod;
                }
            }
    
            ll tmp = 0, sig;
            for(int j = 0;j < i;j++)
            {
                sig = (j+i)&1 ? -1 : 1;
                tmp = (tmp + sig*stir[i][j]*S[j]%mod + mod) % mod;
            }
            S[i] = ((prod - tmp)%mod + mod) % mod;
        }
        return S[k];
    }
    
    int main()
    {
        init();
    
        int T;
        scanf("%d", &T);
        while(T--)
        {
            scanf("%lld%lld", &n, &k);
            printf("%lld
    ", cal());
        }
    
        return 0;
    }
    View Code

    第二类Stirling数

    首先需要证明一个式子

    $$i^k = sum_{j=1}^kS(k, j)*C_i^j*j!$$

    证:对于一个 $i^k$,可以具体理解为把 $k$ 个不同的球放入 $i$ 个不同盒子里的方案数(允许空盒);

    现在,枚举恰好有 $j$ 个盒子放有球,$j$ 可从1取到 $k$,所以方案数为 $sum_{j=1}^kS(k, j)*C_i^j*j!$;

    证毕。

    $sum limits ^{n}_{i=0} i^k = sum limits _{i=0}^{n}sumlimits _{j=1}^{k} S_{k,j}*C_{i,j}*j!$

    然后讨论 $S(k, j)$ 的系数和:$sum limits ^{n}_{i=0} C_{i,j} *j!$,即 $j!* sumlimits^{n}_{i=0} C_{i,j}$

    已知:$sum limits_{i=0}^{n}C_{i,j}=C_{n+1,j+1}$

    所以 $S(k, j)$ 的系数为 $j!*C_{n+1}^{j+1}$,

    于是 $sum limits ^{n}_{i=0} i^k= sumlimits_{j=1}^{k}S(k, j)*j!*C_{n+1}^{j+1}$.

    变成排列数形式:$displaystyle sum limits ^{n}_{i=0} i^k= sumlimits_{i=1}^{k}S(k, i)* frac{P_{n+1}^{i+1}}{{i+1}}$

    然后预处理斯特林数就可以解决了.  

    //同样单次查询是 $O(k^2)$

    #include<bits/stdc++.h>
    using namespace std;
    
    typedef long long ll;
    const ll mod = 1e9 + 7;
    const int maxk = 2000 + 1;
    ll n, k;
    
    ll stir[maxk][maxk];
    void init()
    {
        stir[0][0] = stir[1][1] = 1;
        for(int i = 2;i < maxk;i++)
            for(int j = 1;j <= i;j++)
                stir[i][j] = (stir[i-1][j-1] + j*stir[i-1][j]) % mod;
    }
    
    ll cal()
    {
        n %= mod;
        ll sum = 0;
        for(int i = 1;i <= k;i++)
        {
            ll prod;
            if(i > n)  prod = 0;
            else
            {
                prod = 1;
                for(int j = 0;j <= i;j++)
                {
                    ll tmp = n+1-j;
                    if(tmp % (i+1) == 0)  tmp /= (i+1);
                    prod = prod * tmp % mod;
                }
            }
            //printf("%lld
    ", prod);
            sum = (sum + stir[k][i]*prod) % mod;
        }
        return sum;
    }
    
    int main()
    {
        init();
    
        int T;
        scanf("%d", &T);
        while(T--)
        {
            scanf("%lld%lld", &n, &k);
            printf("%lld
    ", cal());
        }
    
        return 0;
    }
    View Code

    参考链接:

    1. https://www.cnblogs.com/Zerokei/p/9726879.html

    2. https://blog.csdn.net/lyd_7_29/article/details/75041818

  • 相关阅读:
    数据库的三大范式
    mysql中变量的定义
    java实现用两个栈实现队列
    java实现替换空格
    java实现二维数组中查找
    struts2核心配置之Action
    struts2核心配置之struts.xml
    初识struts2
    $.ajax()参数详解
    百度Map-JSAPI-覆盖物范围查询标记
  • 原文地址:https://www.cnblogs.com/lfri/p/11561451.html
Copyright © 2011-2022 走看看