zoukankan      html  css  js  c++  java
  • 第一和第二类斯特林数的学习笔记

    最近在学第一类和第二类斯特林数。这里记录一下学习的知识点/模板还有题目。

    https://blog.csdn.net/litble/article/details/80882581

    https://www.cnblogs.com/y2823774827y/p/10700231.html

    https://www.cnblogs.com/cjyyb/p/10142878.html (强推)

    这篇博文没有什么创新的东西,更多是对上面大佬提出知识的一个摘抄和总结(略去了证明部分),加上一些练习题的学习笔记。

    第一类斯特林数是解决:将n个不同的元素划分为k个圆排列的方案数,递推式为f(i,j)=f(i1,j1)+(i1)f(i1,j)(可以理解为:加入一个新的数有两种情况①是自己成环②是加入以前的环,那么这个数可以插到任何一个数的前面)。

    第一类斯特林数有一些性质

    ①sigma S1(n,i) = n!  ; 这个可以理解为:其实每个1-n的排列就是一个循环置换,那么n的全排列就等于所有的置换方案总和就是sigma S1(n,i) 。

     第一类斯特林数的求法

    用递推式求某个S1(n,k)的话时间是O(n^2)不太理想,一种比较好的做法是根据第一类斯特林数的生成函数:x*(x+1)(x+2)(x+3)……(x+n-1)=Σf[n][i]*x^i ,可以用分治NTT求其生成函数,然后第i项的系数即是S1[n][i]。这样的时间是O(n*log2n^2),其实还有O(n*log2n)的求法,我还没学。

     

    例题+模板:codeforces 960G Bandit Blues

    推式子后发现 ans=S1[n-1][A+B-2]*C(A+B-2,A-1) 。于是主要矛盾就是求S1(n-1,A+B-2)。

     1 #include<bits/stdc++.h>
     2 #define LL long long
     3 using namespace std;
     4 const LL P=998244353,yg=3;
     5 LL A[400010];
     6 LL bin[400010];
     7 
     8 LL power(LL x,LL p) {
     9     LL ret=1;
    10     for (;p;p>>=1) {
    11         if (p&1) ret=(ret*x)%P;
    12         x=(x*x)%P;
    13     }
    14     return ret;
    15 }
    16 
    17 void NTT(LL *a,LL n,LL op) {  //NTT:系数a数组,长度为n,op=1求值op=-1插值 
    18     for(LL i=0;i<n;i++) bin[i]=(bin[i>>1]>>1)|((i&1)*(n>>1));
    19     for(LL i=0;i<n;i++) if(i<bin[i]) swap(a[i],a[bin[i]]);
    20     for(LL i=1;i<n;i<<=1) {
    21         LL wn=power(yg,op==1?(P-1)/(2*i):(P-1)-(P-1)/(2*i)),w,t;
    22         for(LL j=0;j<n;j+=i<<1) {
    23             w=1;
    24             for(LL k=0;k<i;k++) {
    25                 t=a[i+j+k]*w%P;w=w*wn%P;
    26                 a[i+j+k]=(a[j+k]-t+P)%P;a[j+k]=(a[j+k]+t)%P;
    27             }
    28         }
    29     }
    30     if(op==-1) {
    31         LL Inv=power(n,P-2);
    32         for(LL i=0;i<n;i++) a[i]=a[i]*Inv%P;
    33     }
    34 }
    35 
    36 LL n,a,b;
    37 void solve(LL *a,LL len,LL l,LL r) {  //分治NTT求第一类斯特林数 
    38     if(l==r) {a[0]=l;a[1]=1;return;}  //分治边界 
    39     LL mid=(l+r)/2; LL a1[len],a2[len];
    40     memset(a1,0,sizeof(a1));memset(a2,0,sizeof(a2));
    41     solve(a1,len>>1,l,mid);solve(a2,len>>1,mid+1,r);  //分治,先求两边 
    42     NTT(a1,len,1);NTT(a2,len,1);
    43     for(LL i=0;i<len;i++) a[i]=a1[i]*a2[i]%P;  //两边NTT结果相乘得到[l,r]的结果 
    44     NTT(a,len,-1);
    45 }
    46 
    47 LL C(LL m,LL n) {  //求组合数C(m,n) 
    48     LL fac1=1,fac2=1;
    49     for(LL i=1;i<=n;i++) (fac1*=i)%=P,(fac2*=(m-i+1))%=P;
    50     return fac2*power(fac1,P-2)%P;
    51 }
    52 
    53 int main()
    54 {
    55     scanf("%lld%lld%lld",&n,&a,&b);
    56     if(a+b-2>n-1||!a||!b) return puts("0"),0;
    57     if(n==1) return puts("1"),0;
    58     
    59     LL N=n-1,M=a+b-2;
    60     LL len=1;while(len<(n+1)<<1) len<<=1;
    61     solve(A,len,0,N-1);  //求S1(n-1,i)这一行的值 
    62     
    63     printf("%lld",A[M]*C(a+b-2,a-1)%P);
    64 }
    View Code

    第二类斯特林数

    第二类斯特林数是将n个不同的元素放入m个相同盒子里,每个盒子非空的方案数。

    那么S2(n,m)地递推式也比较好推,S2(n,m)=S2(n-1,m-1) + m*S2(n-1,m) ; (新来的一个元素是自己放入一个新盒子还是放到以前的盒子里)

    第二类斯特林数的性质

     m^n理解为把m个不同元素放到n个不同盒子的方案数,那么我们可以枚举恰好要放i个盒子(即i个盒子非空),然后选出这i个盒子乘上n个球放到i个盒子方案数,注意此时盒子是相同的,那么我们乘上i!使得盒子边的不同。

    这个式子可以化成上升/下降幂的形式

     ②第二类斯特林数和自然数幂和的关系:

    第二类斯特林数的求法

    我们写出第二类斯特林式和组合意义下的式子并化简,初始式子的意思是:我们枚举并选出k个必须空的盒子,然后把元素随便放在剩余的盒子里。这样我们就得到了至少k个空盒子的方案数,我们对这个方案数容斥一下就得到n个不同元素放到m个不同盒子方案数,对这个结果再除以m!就得到了S2(n,m)。

    图借用一下上面的yyb大佬的博客,看到这个东西很想卷积,这样我们就能用FFT/NTT求在O(nlogn)的时间内求S2(n,m)的某一行啦。

    那么令f(i)=(-1)^i/i!  g[i]=i^n/i!  那么S2(n)=f.g  第m项系数就是S2(n,m)

    NTT求第二类斯特林数模板:洛谷P5395 第二类斯特林数·行:

     1 #include<bits/stdc++.h>
     2 #define LL long long
     3 using namespace std;
     4 const int N=1e6+10;
     5 const LL P=167772161,yg=3;
     6 LL n,fac[N],inv[N],f[N],g[N],S2[N];
     7 LL bin[N];
     8 
     9 LL power(LL x,LL p) {
    10     LL ret=1;
    11     for (;p;p>>=1) {
    12         if (p&1) ret=(ret*x)%P;
    13         x=(x*x)%P;
    14     }
    15     return ret;
    16 }
    17 
    18 void NTT(LL *a,LL n,LL op) {  //NTT:系数a数组,长度为n,op=1求值op=-1插值 
    19     for(LL i=0;i<n;i++) bin[i]=(bin[i>>1]>>1)|((i&1)*(n>>1));
    20     for(LL i=0;i<n;i++) if(i<bin[i]) swap(a[i],a[bin[i]]);
    21     for(LL i=1;i<n;i<<=1) {
    22         LL wn=power(yg,op==1?(P-1)/(2*i):(P-1)-(P-1)/(2*i)),w,t;
    23         for(LL j=0;j<n;j+=i<<1) {
    24             w=1;
    25             for(LL k=0;k<i;k++) {
    26                 t=a[i+j+k]*w%P;w=w*wn%P;
    27                 a[i+j+k]=(a[j+k]-t+P)%P;a[j+k]=(a[j+k]+t)%P;
    28             }
    29         }
    30     }
    31     if(op==-1) {
    32         LL Inv=power(n,P-2);
    33         for(LL i=0;i<n;i++) a[i]=a[i]*Inv%P;
    34     }
    35 }
    36 
    37 int main()
    38 {
    39     cin>>n;
    40     fac[0]=inv[0]=1;
    41     for (int i=1;i<=n;i++) fac[i]=fac[i-1]*i%P,inv[i]=power(fac[i],P-2);
    42     for (int i=0;i<=n;i++) f[i]=(power(-1,i)+P)%P*inv[i]%P;
    43     for (int i=0;i<=n;i++) g[i]=power(i,n)*inv[i]%P;
    44     
    45     LL len=1;while(len<(n+1)<<1) len<<=1;
    46     
    47     NTT(f,len,1); NTT(g,len,1);
    48     for (int i=0;i<len;i++) S2[i]=(f[i]*g[i])%P;  //求f.g的卷积为S2 
    49     NTT(S2,len,-1);
    50     
    51     for (int i=0;i<=n;i++) printf("%lld ",S2[i]);  //求第n行的斯特林数 
    52     return 0;
    53 }
    View Code

    相关题目

    上面大佬将与斯特林相关的题目分成了四类:①函数与斯特林数公式相同:就是按照题目推式子结果发现就是斯特林公式 ②直接推式:就是题目就是让你求一个与斯特林数相关的式子 ③根据题意运用斯特林函数及公式 ④斯特林反演的运用 :容斥类问题,运用斯特林反演解决。

    codeforces 960G Bandit Blues

    上面提到的例题,推式子后发现 ans=S1[n-1][A+B-2]*C(A+B-2,A-1) 。于是先NTT求S1(n-1,A+B-2)乘上组合数即可。

    View Code

    洛谷P4091 [HEOI2016/TJOI2016]求和

    这道题挺考验推式子能力的,题解参考https://www.cnblogs.com/y2823774827y/p/10709820.html这位大佬和litble大佬的。

    化简结果是  那么令 令

    结果就是

     1 #include<bits/stdc++.h>
     2 #define LL long long
     3 using namespace std;
     4 const int N=4e5+10;
     5 const LL P=998244353,yg=3;
     6 LL n,fac[N],inv[N],f[N],g[N],S2[N];
     7 LL bin[N];
     8 
     9 LL power(LL x,LL p) {
    10     LL ret=1;
    11     for (;p;p>>=1) {
    12         if (p&1) ret=(ret*x)%P;
    13         x=(x*x)%P;
    14     }
    15     return ret;
    16 }
    17 
    18 void NTT(LL *a,LL n,LL op) {  //NTT:系数a数组,长度为n,op=1求值op=-1插值 
    19     for(LL i=0;i<n;i++) bin[i]=(bin[i>>1]>>1)|((i&1)*(n>>1));
    20     for(LL i=0;i<n;i++) if(i<bin[i]) swap(a[i],a[bin[i]]);
    21     for(LL i=1;i<n;i<<=1) {
    22         LL wn=power(yg,op==1?(P-1)/(2*i):(P-1)-(P-1)/(2*i)),w,t;
    23         for(LL j=0;j<n;j+=i<<1) {
    24             w=1;
    25             for(LL k=0;k<i;k++) {
    26                 t=a[i+j+k]*w%P;w=w*wn%P;
    27                 a[i+j+k]=(a[j+k]-t+P)%P;a[j+k]=(a[j+k]+t)%P;
    28             }
    29         }
    30     }
    31     if(op==-1) {
    32         LL Inv=power(n,P-2);
    33         for(LL i=0;i<n;i++) a[i]=a[i]*Inv%P;
    34     }
    35 }
    36 
    37 int main()
    38 {
    39     cin>>n;
    40     fac[0]=inv[0]=1;
    41     for (int i=1;i<=n;i++) fac[i]=fac[i-1]*i%P,inv[i]=power(fac[i],P-2);
    42     
    43     for (int i=0;i<=n;i++) f[i]=(power(-1,i)+P)%P*inv[i]%P;
    44     for (int i=0;i<=n;i++) g[i]=(power(i,n+1)-1+P)%P*power((i-1+P)%P,P-2)%P*inv[i]%P;
    45     g[1]=n+1;
    46     
    47     LL N=n-1;
    48     LL len=1;while(len<(n+1)<<1) len<<=1;
    49     
    50     NTT(f,len,1); NTT(g,len,1);
    51     for (int i=0;i<len;i++) S2[i]=(f[i]*g[i])%P;  //求f.g的卷积为S2 
    52     NTT(S2,len,-1);
    53     
    54     LL ans=0;
    55     for (int i=0;i<=n;i++) {
    56         LL tmp=power(2,i)*fac[i]%P*S2[i]%P;
    57         ans=(ans+tmp)%P;
    58     }
    59     cout<<ans<<endl;    
    60     return 0;
    61 }
    View Code
  • 相关阅读:
    使用mybetis插件的公共方法进行查询
    bootstrap的刷新和查询
    消息队列的使用和注意事项
    MySQL 日期时间计算函数
    mysql基础学习网站
    html装换成字符串的工具、代码辅助工具
    正则校验数字,数字保留两位小数,字母,特殊符号和数字
    HTML表格和表单
    HTML标签
    媒体查询写法
  • 原文地址:https://www.cnblogs.com/clno1/p/10809678.html
Copyright © 2011-2022 走看看