第一和第二類斯特林數的學習筆記


最近在學第一類和第二類斯特林數。這里記錄一下學習的知識點/模板還有題目。

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

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM