最近在學第一類和第二類斯特林數。這裏記錄一下學習的知識點/模板還有題目。html
http://www.javashuo.com/article/p-wroajijt-nm.htmlc++
http://www.javashuo.com/article/p-vorktlpq-dc.html數組
http://www.javashuo.com/article/p-nbuftrhx-em.html (強推)ide
這篇博文沒有什麼創新的東西,更可能是對上面大佬提出知識的一個摘抄和總結(略去了證實部分),加上一些練習題的學習筆記。函數
第一類斯特林數是解決:將n個不一樣的元素劃分爲k個圓排列的方案數,遞推式爲f(i,j)=f(i−1,j−1)+(i−1)f(i−1,j)(能夠理解爲:加入一個新的數有兩種狀況①是本身成環②是加入之前的環,那麼這個數能夠插到任何一個數的前面)。學習
第一類斯特林數有一些性質:spa
①sigma S1(n,i) = n! ; 這個能夠理解爲:其實每一個1-n的排列就是一個循環置換,那麼n的全排列就等於全部的置換方案總和就是sigma S1(n,i) 。.net
②3d
③code
第一類斯特林數的求法
用遞推式求某個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 }
第二類斯特林數:
第二類斯特林數是將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 }
相關題目:
上面大佬將與斯特林相關的題目分紅了四類:①函數與斯特林數公式相同:就是按照題目推式子結果發現就是斯特林公式 ②直接推式:就是題目就是讓你求一個與斯特林數相關的式子 ③根據題意運用斯特林函數及公式 ④斯特林反演的運用 :容斥類問題,運用斯特林反演解決。
codeforces 960G Bandit Blues
上面提到的例題,推式子後發現 ans=S1[n-1][A+B-2]*C(A+B-2,A-1) 。因而先NTT求S1(n-1,A+B-2)乘上組合數便可。
洛谷P4091 [HEOI2016/TJOI2016]求和
這道題挺考驗推式子能力的,題解參考http://www.javashuo.com/article/p-syrzpylc-md.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 }