斯特林数和分拆数

上升幂与下降幂

  • 上升幂:xn¯=k=0n1(x+k)=x(x+1)(x+2)...(x+n1)
  • 下降幂:xn_=x!(xn)!=k=0n1(xk)

第一类斯特林数(无符号)

  • 定义:第一类斯特林数(斯特林轮换数)[nk],也可记做s(n,k) ,表示将n个两两不同的元素,划分为k个互不区分的非空轮换的方案数。一个轮换就是一个首尾相接的环形排列。我们可以写出一个轮换[A,B,C,D] ,并且我们认为[A,B,C,D]=[B,C,D,A]=[C,D,A,B]=[D,A,B,C] ,即,两个可以通过旋转而互相得到的轮换是等价的。注意,我们不认为两个可以通过翻转而相互得到的轮换等价,即[A,B,C,D][D,C,B,A] 。(另一种理解:将 n 个元素,划分为 k 个圆排列的方案数)

  • 递推式:[nk]=[n1k1]+(n1)[n1k]。边界[n0]=[n=0]

    组合意义:将该新元素置于一个单独的轮换中,共有[n1k1]中方案;将该元素插入到任何一个现有的轮换中,共有(n1)[n1k]种方案。

  • 性质

    k=0n[nk]=n!

    [nn1]=(n2)

    [n2]=(n1)!i=1n11i

    [n1]=(n1)!

生成函数(同行) 上升幂:Fn(x)=xn¯=i=0n1(x+i)=(x+n1)!(x1)!=i=0n[ni]xi
O(nlogn)

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    //freopen("gen.in","r",stdin);
    Poly::init(19);
    auto CDQ=[&](auto self,int l,int r)->Poly::poly{
        if(l==r) return Poly::poly({l,1});
        int mid=l+r>>1;
        return Poly::poly_mul(self(self,l,mid),self(self,mid+1,r));
    };
    int n;
    cin>>n;
    Poly::poly F=CDQ(CDQ,0,n-1);
    for(int i=0;i<=n;i++) cout<<F[i]<<" ";
    return 0;
}

生成函数(同列):F(x)=i=1n(i1)!xii!=i=1nxii=i=1n[ik]xi

第二类斯特林数(无符号)

  • 定义:第二类斯特林数(斯特林子集数){nk},也可记做S(n,k) ,表示将n个两两不同的元素,划分为k互不区分的非空子集的方案数

  • 递推式:{nk}={n1k1}+k{n1k}

    组合意义:插入一个新元素时,可以将这个元素单独放入一个子集,也可以将这个元素放入现有的一个非空子集

  • 通项公式:{nm}=i=0m(1)miini!(mi)!

  • 性质

    k=0n{nk}=BnBn为贝尔数

    nk=i=1k{ki}×i!×(ni)=i=1k{ki}×ni_,这个可以用来求自然数数幂和:i=1nik=i=1nj=0k{kj}×j!×(ij)=j=0k{kj}×j!i=1n(ij)=j=0k{kj}×j!×(n+1j+1)=j=0k{kj}(n+1)j+1_(j+1)!

  • 同一行第二类斯特林数通项公式计算O(nlogn)

int main() {
    scanf("%d", &n);
    poly f(n + 1), g(n + 1);
    for (int i = 0; i <= n; ++i)
        g[i] = (i & 1 ? mod - 1ll : 1ll) * infac[i] % mod,
        f[i] = 1ll * qpow(i, n) * infac[i] % mod;
    poly F=poly_mul(f,g);
    F.resize(n + 1);
    for (int i = 0; i <= n; ++i)
        printf("%d ", F[i]);
    return 0;
}

分拆数

  • 定义:将正整数n进行整数拆分的方案数

  • 求法:

    • 完全背包,把数字1n看做价值为1,容量为i的物品,进行完全背包
    • 五边形定理
      #include <stdio.h>
      
      long long a[100010];
      long long p[50005];
      
      int main() {
      p[0] = 1;
      p[1] = 1;
      p[2] = 2;
      int i;
      for (i = 1; i < 50005;i++) /*递推式系数1,2,5,7,12,15,22,26...i*(3*i-1)/2,i*(3*i+1)/2*/
      {
          a[2 * i] = i * (i * 3 - 1) / 2; /*五边形数为1,5,12,22...i*(3*i-1)/2*/
          a[2 * i + 1] = i * (i * 3 + 1) / 2;
      }
      for (  i = 3; i < 50005; i++) /*p[n]=p[n-1]+p[n-2]-p[n-5]-p[n-7]+p[12]+p[15]-...+p[n-i*[3i-1]/2]+p[n-i*[3i+1]/2]*/
      {
          p[i] = 0;
          int j;
          for (j = 2; a[j] <= i; j++) /*有可能为负数,式中加1000007*/
          {
          if (j & 2) {
              p[i] = (p[i] + p[i - a[j]] + 1000007) % 1000007;
          } else {
              p[i] = (p[i] - p[i - a[j]] + 1000007) % 1000007;
          }
          }
      }
      int n;
      while (~scanf("%d", &n)) {
          printf("%lld\n", p[n]);
      }
      }
      
  • k个部分的分拆,记作p(n,k)

    p(n,k)=p(n1,k1)+p(nk,k)

例题

2021CCPC Guangzhou A

参考:OIWiki

posted @   Arashimu  阅读(156)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· winform 绘制太阳,地球,月球 运作规律
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· AI 智能体引爆开源社区「GitHub 热点速览」
· Manus的开源复刻OpenManus初探
· 写一个简单的SQL生成工具
点击右上角即可分享
微信分享提示