1. 程式人生 > >第一和第二類斯特林數的學習

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

排列 using ron amp urn ons 生成函數 時間 www

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

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),用遞推式求某個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)

#include<bits/stdc++.h>
#define LL long long
using namespace std;
const LL P=998244353,yg=3; LL A[400010]; LL bin[400010]; LL power(LL x,LL p) { LL ret=1; for (;p;p>>=1) { if (p&1) ret=(ret*x)%P; x=(x*x)%P; } return ret; } void NTT(LL *a,LL n,LL op) { //NTT:系數a數組,長度為n,op=1求值op=-1插值 for(LL i=0;i<n;i++) bin[i]=(bin[i>>1
]>>1)|((i&1)*(n>>1)); for(LL i=0;i<n;i++) if(i<bin[i]) swap(a[i],a[bin[i]]); for(LL i=1;i<n;i<<=1) { LL wn=power(yg,op==1?(P-1)/(2*i):(P-1)-(P-1)/(2*i)),w,t; for(LL j=0;j<n;j+=i<<1) { w=1; for(LL k=0;k<i;k++) { t=a[i+j+k]*w%P;w=w*wn%P; a[i+j+k]=(a[j+k]-t+P)%P;a[j+k]=(a[j+k]+t)%P; } } } if(op==-1) { LL Inv=power(n,P-2); for(LL i=0;i<n;i++) a[i]=a[i]*Inv%P; } } LL n,a,b; void solve(LL *a,LL len,LL l,LL r) { //分治NTT求第一類斯特林數 if(l==r) {a[0]=l;a[1]=1;return;} //分治邊界 LL mid=(l+r)/2; LL a1[len],a2[len]; memset(a1,0,sizeof(a1));memset(a2,0,sizeof(a2)); solve(a1,len>>1,l,mid);solve(a2,len>>1,mid+1,r); //分治,先求兩邊 NTT(a1,len,1);NTT(a2,len,1); for(LL i=0;i<len;i++) a[i]=a1[i]*a2[i]%P; //兩邊NTT結果相乘得到[l,r]的結果 NTT(a,len,-1); } LL C(LL m,LL n) { //求組合數C(m,n) LL fac1=1,fac2=1; for(LL i=1;i<=n;i++) (fac1*=i)%=P,(fac2*=(m-i+1))%=P; return fac2*power(fac1,P-2)%P; } int main() { scanf("%lld%lld%lld",&n,&a,&b); if(a+b-2>n-1||!a||!b) return puts("0"),0; if(n==1) return puts("1"),0; LL N=n-1,M=a+b-2; LL len=1;while(len<(n+1)<<1) len<<=1; solve(A,len,0,N-1); //求S1(n-1,i)這一行的值 printf("%lld",A[M]*C(a+b-2,a-1)%P); }

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