Why Introduce Stirling Numbers of the Second Kind First?
Although called the "second kind," Stirling numbers of the second kind were first described in Stirling's works and in Concrete Mathematics, and are also much more commonly used than Stirling numbers of the first kind.
Stirling numbers of the second kind (Stirling subset numbers) \(\begin{Bmatrix}n\\ k\end{Bmatrix}\), also denoted as \(S(n,k)\), represent the number of ways to partition \(n\) distinct elements into \(k\) indistinguishable non-empty subsets.
This formula can be proved using the principle of inclusion-exclusion. Let \(G_i\) be the number of ways to partition \(n\) distinct elements into \(i\) distinct sets (allowing empty sets), and let \(F_i\) be the number of ways to partition \(n\) distinct elements into \(i\) distinct non-empty sets (disallowing empty sets).
Consider the relationship between \(F_i\) and \(\begin{Bmatrix}n\\i\end{Bmatrix}\). Stirling numbers of the second kind require the subsets to be indistinguishable, so \(F_i\) is exactly \(i!\) times \(\begin{Bmatrix}n\\i\end{Bmatrix}\). Thus,
Computing All Stirling Numbers of the Second Kind for a Given Row
"Row" here refers to the set of \(\begin{Bmatrix}n\\i\end{Bmatrix}\) for fixed \(n\) and varying \(i\). Computing all Stirling numbers in a row means finding, for \(i=0..n\), the number of ways to partition \(n\) distinct elements into \(i\) non-empty subsets.
Based on the closed-form formula above, convolution can be used. This approach has a time complexity of \(O(n \log n)\).
The following code uses a polynomial class named poly for reference.
#ifndef _FEISTDLIB_POLY_#define _FEISTDLIB_POLY_/* * This file is part of the fstdlib project. * Version: Build v0.0.2 * You can check for details at https://github.com/FNatsuka/fstdlib */#include<algorithm>#include<cmath>#include<cstdio>#include<vector>namespacefstdlib{usingll=longlong;intmod=998244353,grt=3;classpoly{private:std::vector<int>data;voidout(void){for(inti=0;i<(int)data.size();++i)printf("%d ",data[i]);puts("");}public:poly(std::size_tlen=std::size_t(0)){data=std::vector<int>(len);}poly(conststd::vector<int>&b){data=b;}poly(constpoly&b){data=b.data;}voidresize(std::size_tlen,intval=0){data.resize(len,val);}std::size_tsize(void)const{returndata.size();}voidclear(void){data.clear();}#if __cplusplus >= 201103Lvoidshrink_to_fit(void){data.shrink_to_fit();}#endifint&operator[](std::size_tb){returndata[b];}constint&operator[](std::size_tb)const{returndata[b];}polyoperator*(constpoly&h)const;polyoperator*=(constpoly&h);polyoperator*(constint&h)const;polyoperator*=(constint&h);polyoperator+(constpoly&h)const;polyoperator+=(constpoly&h);polyoperator-(constpoly&h)const;polyoperator-=(constpoly&h);polyoperator<<(conststd::size_t&b)const;polyoperator<<=(conststd::size_t&b);polyoperator>>(conststd::size_t&b)const;polyoperator>>=(conststd::size_t&b);polyoperator/(constint&h)const;polyoperator/=(constint&h);polyoperator==(constpoly&h)const;polyoperator!=(constpoly&h)const;polyoperator+(constint&h)const;polyoperator+=(constint&h);polyinv(void)const;polyinv(constint&h)const;friendpolysqrt(constpoly&h);friendpolylog(constpoly&h);friendpolyexp(constpoly&h);};intqpow(inta,intb,intp=mod){intres=1;while(b){if(b&1)res=(ll)res*a%p;a=(ll)a*a%p,b>>=1;}returnres;}std::vector<int>rev;voiddft_for_module(std::vector<int>&f,intn,intb){staticstd::vector<int>w;w.resize(n);for(inti=0;i<n;++i)if(i<rev[i])std::swap(f[i],f[rev[i]]);for(inti=2;i<=n;i<<=1){w[0]=1,w[1]=qpow(grt,(mod-1)/i);if(b==-1)w[1]=qpow(w[1],mod-2);for(intj=2;j<i/2;++j)w[j]=(ll)w[j-1]*w[1]%mod;for(intj=0;j<n;j+=i)for(intk=0;k<i/2;++k){intp=f[j+k],q=(ll)f[j+k+i/2]*w[k]%mod;f[j+k]=(p+q)%mod,f[j+k+i/2]=(p-q+mod)%mod;}}}polypoly::operator*(constpoly&h)const{intN=1;while(N<(int)(size()+h.size()-1))N<<=1;std::vector<int>f(this->data),g(h.data);f.resize(N),g.resize(N);rev.resize(N);for(inti=0;i<N;++i)rev[i]=(rev[i>>1]>>1)|(i&1?N>>1:0);dft_for_module(f,N,1),dft_for_module(g,N,1);for(inti=0;i<N;++i)f[i]=(ll)f[i]*g[i]%mod;dft_for_module(f,N,-1),f.resize(size()+h.size()-1);for(inti=0,inv=qpow(N,mod-2);i<(int)f.size();++i)f[i]=(ll)f[i]*inv%mod;returnf;}polypoly::operator*=(constpoly&h){return*this=*this*h;}polypoly::operator*(constint&h)const{std::vector<int>f(this->data);for(inti=0;i<(int)f.size();++i)f[i]=(ll)f[i]*h%mod;returnf;}polypoly::operator*=(constint&h){for(inti=0;i<(int)size();++i)data[i]=(ll)data[i]*h%mod;return*this;}polypoly::operator+(constpoly&h)const{std::vector<int>f(this->data);if(f.size()<h.size())f.resize(h.size());for(inti=0;i<(int)h.size();++i)f[i]=(f[i]+h[i])%mod;returnf;}polypoly::operator+=(constpoly&h){std::vector<int>&f=this->data;if(f.size()<h.size())f.resize(h.size());for(inti=0;i<(int)h.size();++i)f[i]=(f[i]+h[i])%mod;returnf;}polypoly::operator-(constpoly&h)const{std::vector<int>f(this->data);if(f.size()<h.size())f.resize(h.size());for(inti=0;i<(int)h.size();++i)f[i]=(f[i]-h[i]+mod)%mod;returnf;}polypoly::operator-=(constpoly&h){std::vector<int>&f=this->data;if(f.size()<h.size())f.resize(h.size());for(inti=0;i<(int)h.size();++i)f[i]=(f[i]-h[i]+mod)%mod;returnf;}polypoly::operator<<(conststd::size_t&b)const{std::vector<int>f(size()+b);for(inti=0;i<(int)size();++i)f[i+b]=data[i];returnf;}polypoly::operator<<=(conststd::size_t&b){return*this=(*this)<<b;}polypoly::operator>>(conststd::size_t&b)const{std::vector<int>f(size()-b);for(inti=0;i<(int)f.size();++i)f[i]=data[i+b];returnf;}polypoly::operator>>=(conststd::size_t&b){return*this=(*this)>>b;}polypoly::operator/(constint&h)const{std::vector<int>f(this->data);intinv=qpow(h,mod-2);for(inti=0;i<(int)f.size();++i)f[i]=(ll)f[i]*inv%mod;returnf;}polypoly::operator/=(constint&h){intinv=qpow(h,mod-2);for(inti=0;i<(int)data.size();++i)data[i]=(ll)data[i]*inv%mod;return*this;}polypoly::inv(void)const{intN=1;while(N<(int)(size()+size()-1))N<<=1;std::vector<int>f(N),g(N),d(this->data);d.resize(N),f[0]=qpow(d[0],mod-2);for(intw=2;w<N;w<<=1){for(inti=0;i<w;++i)g[i]=d[i];rev.resize(w<<1);for(inti=0;i<w*2;++i)rev[i]=(rev[i>>1]>>1)|(i&1?w:0);dft_for_module(f,w<<1,1),dft_for_module(g,w<<1,1);for(inti=0;i<w*2;++i)f[i]=(ll)f[i]*(2+mod-(ll)f[i]*g[i]%mod)%mod;dft_for_module(f,w<<1,-1);for(inti=0,inv=qpow(w<<1,mod-2);i<w;++i)f[i]=(ll)f[i]*inv%mod;for(inti=w;i<w*2;++i)f[i]=0;}f.resize(size());returnf;}polypoly::operator==(constpoly&h)const{if(size()!=h.size())return0;for(inti=0;i<(int)size();++i)if(data[i]!=h[i])return0;return1;}polypoly::operator!=(constpoly&h)const{if(size()!=h.size())return1;for(inti=0;i<(int)size();++i)if(data[i]!=h[i])return1;return0;}polypoly::operator+(constint&h)const{polyf(this->data);f[0]=(f[0]+h)%mod;returnf;}polypoly::operator+=(constint&h){return*this=(*this)+h;}polypoly::inv(constint&h)const{polyf(*this);f.resize(h);returnf.inv();}intmodsqrt(inth,intp=mod){return1;}polysqrt(constpoly&h){intN=1;while(N<(int)(h.size()+h.size()-1))N<<=1;polyf(N),g(N),d(h);d.resize(N),f[0]=modsqrt(d[0]);for(intw=2;w<N;w<<=1){g.resize(w);for(inti=0;i<w;++i)g[i]=d[i];f=(f+f.inv(w)*g)/2;f.resize(w);}f.resize(h.size());returnf;}polylog(constpoly&h){polyf(h);for(inti=1;i<(int)f.size();++i)f[i-1]=(ll)f[i]*i%mod;f[f.size()-1]=0,f=f*h.inv(),f.resize(h.size());for(inti=(int)f.size()-1;i>0;--i)f[i]=(ll)f[i-1]*qpow(i,mod-2)%mod;f[0]=0;returnf;}polyexp(constpoly&h){intN=1;while(N<(int)(h.size()+h.size()-1))N<<=1;polyf(N),g(N),d(h);f[0]=1,d.resize(N);for(intw=2;w<N;w<<=1){f.resize(w),g.resize(w);for(inti=0;i<w;++i)g[i]=d[i];f=f*(g+1-log(f));f.resize(w);}f.resize(h.size());returnf;}structcomp{longdoublex,y;comp(longdouble_x=0,longdouble_y=0):x(_x),y(_y){}compoperator*(constcomp&b)const{returncomp(x*b.x-y*b.y,x*b.y+y*b.x);}compoperator+(constcomp&b)const{returncomp(x+b.x,y+b.y);}compoperator-(constcomp&b)const{returncomp(x-b.x,y-b.y);}compconj(void){returncomp(x,-y);}};constintEPS=1e-9;template<typenameFLOAT_T>FLOAT_Tfabs(constFLOAT_T&x){returnx>0?x:-x;}template<typenameFLOAT_T>FLOAT_Tsin(constFLOAT_T&x,constlongdouble&EPS=fstdlib::EPS){FLOAT_Tres=0,delt=x;intd=0;while(fabs(delt)>EPS){res+=delt,++d;delt*=-x*x/((2*d)*(2*d+1));}returnres;}template<typenameFLOAT_T>FLOAT_Tcos(constFLOAT_T&x,constlongdouble&EPS=fstdlib::EPS){FLOAT_Tres=0,delt=1;intd=0;while(fabs(delt)>EPS){res+=delt,++d;delt*=-x*x/((2*d)*(2*d-1));}returnres;}constlongdoublePI=std::acos((longdouble)(-1));voiddft_for_complex(std::vector<comp>&f,intn,intb){staticstd::vector<comp>w;w.resize(n);for(inti=0;i<n;++i)if(i<rev[i])std::swap(f[i],f[rev[i]]);for(inti=2;i<=n;i<<=1){w[0]=comp(1,0),w[1]=comp(cos(2*PI/i),b*sin(2*PI/i));for(intj=2;j<i/2;++j)w[j]=w[j-1]*w[1];for(intj=0;j<n;j+=i)for(intk=0;k<i/2;++k){compp=f[j+k],q=f[j+k+i/2]*w[k];f[j+k]=p+q,f[j+k+i/2]=p-q;}}}classarbitrary_module_poly{private:std::vector<int>data;intconstruct_element(intD,llx,lly,llz)const{x%=mod,y%=mod,z%=mod;return((ll)D*D*x%mod+(ll)D*y%mod+z)%mod;}public:intmod;arbitrary_module_poly(std::size_tlen=std::size_t(0),intmodule_value=1e9+7){mod=module_value;data=std::vector<int>(len);}arbitrary_module_poly(conststd::vector<int>&b,intmodule_value=1e9+7){mod=module_value;data=b;}arbitrary_module_poly(constarbitrary_module_poly&b){mod=b.mod;data=b.data;}voidresize(std::size_tlen,constint&val=0){data.resize(len,val);}std::size_tsize(void)const{returndata.size();}voidclear(void){data.clear();}#if __cplusplus >= 201103Lvoidshrink_to_fit(void){data.shrink_to_fit();}#endifint&operator[](std::size_tb){returndata[b];}constint&operator[](std::size_tb)const{returndata[b];}arbitrary_module_polyoperator*(constarbitrary_module_poly&h)const;arbitrary_module_polyoperator*=(constarbitrary_module_poly&h);arbitrary_module_polyoperator*(constint&h)const;arbitrary_module_polyoperator*=(constint&h);arbitrary_module_polyoperator+(constarbitrary_module_poly&h)const;arbitrary_module_polyoperator+=(constarbitrary_module_poly&h);arbitrary_module_polyoperator-(constarbitrary_module_poly&h)const;arbitrary_module_polyoperator-=(constarbitrary_module_poly&h);arbitrary_module_polyoperator<<(conststd::size_t&b)const;arbitrary_module_polyoperator<<=(conststd::size_t&b);arbitrary_module_polyoperator>>(conststd::size_t&b)const;arbitrary_module_polyoperator>>=(conststd::size_t&b);arbitrary_module_polyoperator/(constint&h)const;arbitrary_module_polyoperator/=(constint&h);arbitrary_module_polyoperator==(constarbitrary_module_poly&h)const;arbitrary_module_polyoperator!=(constarbitrary_module_poly&h)const;arbitrary_module_polyinv(void)const;arbitrary_module_polyinv(constint&h)const;friendarbitrary_module_polysqrt(constarbitrary_module_poly&h);friendarbitrary_module_polylog(constarbitrary_module_poly&h);};arbitrary_module_polyarbitrary_module_poly::operator*(constarbitrary_module_poly&h)const{intN=1;while(N<(int)(size()+h.size()-1))N<<=1;std::vector<comp>f(N),g(N),p(N),q(N);constintD=std::sqrt(mod);for(inti=0;i<(int)size();++i)f[i].x=data[i]/D,f[i].y=data[i]%D;for(inti=0;i<(int)h.size();++i)g[i].x=h[i]/D,g[i].y=h[i]%D;rev.resize(N);for(inti=0;i<N;++i)rev[i]=(rev[i>>1]>>1)|(i&1?N>>1:0);dft_for_complex(f,N,1),dft_for_complex(g,N,1);for(inti=0;i<N;++i){p[i]=(f[i]+f[(N-i)%N].conj())*comp(0.50,0)*g[i];q[i]=(f[i]-f[(N-i)%N].conj())*comp(0,-0.5)*g[i];}dft_for_complex(p,N,-1),dft_for_complex(q,N,-1);std::vector<int>r(size()+h.size()-1);for(inti=0;i<(int)r.size();++i)r[i]=construct_element(D,p[i].x/N+0.5,(p[i].y+q[i].x)/N+0.5,q[i].y/N+0.5);returnarbitrary_module_poly(r,mod);}arbitrary_module_polyarbitrary_module_poly::operator*=(constarbitrary_module_poly&h){return*this=*this*h;}arbitrary_module_polyarbitrary_module_poly::operator*(constint&h)const{std::vector<int>f(this->data);for(inti=0;i<(int)f.size();++i)f[i]=(ll)f[i]*h%mod;returnarbitrary_module_poly(f,mod);}arbitrary_module_polyarbitrary_module_poly::operator*=(constint&h){for(inti=0;i<(int)size();++i)data[i]=(ll)data[i]*h%mod;return*this;}arbitrary_module_polyarbitrary_module_poly::operator+(constarbitrary_module_poly&h)const{std::vector<int>f(this->data);if(f.size()<h.size())f.resize(h.size());for(inti=0;i<(int)h.size();++i)f[i]=(f[i]+h[i])%mod;returnarbitrary_module_poly(f,mod);}arbitrary_module_polyarbitrary_module_poly::operator+=(constarbitrary_module_poly&h){if(size()<h.size())resize(h.size());for(inti=0;i<(int)h.size();++i)data[i]=(data[i]+h[i])%mod;return*this;}arbitrary_module_polyarbitrary_module_poly::operator-(constarbitrary_module_poly&h)const{std::vector<int>f(this->data);if(f.size()<h.size())f.resize(h.size());for(inti=0;i<(int)h.size();++i)f[i]=(f[i]+mod-h[i])%mod;returnarbitrary_module_poly(f,mod);}arbitrary_module_polyarbitrary_module_poly::operator-=(constarbitrary_module_poly&h){if(size()<h.size())resize(h.size());for(inti=0;i<(int)h.size();++i)data[i]=(data[i]+mod-h[i])%mod;return*this;}arbitrary_module_polyarbitrary_module_poly::operator<<(conststd::size_t&b)const{std::vector<int>f(size()+b);for(inti=0;i<(int)size();++i)f[i+b]=data[i];returnarbitrary_module_poly(f,mod);}arbitrary_module_polyarbitrary_module_poly::operator<<=(conststd::size_t&b){return*this=(*this)<<b;}arbitrary_module_polyarbitrary_module_poly::operator>>(conststd::size_t&b)const{std::vector<int>f(size()-b);for(inti=0;i<(int)f.size();++i)f[i]=data[i+b];returnarbitrary_module_poly(f,mod);}arbitrary_module_polyarbitrary_module_poly::operator>>=(conststd::size_t&b){return*this=(*this)>>b;}arbitrary_module_polyarbitrary_module_poly::inv(void)const{intN=1;while(N<(int)(size()+size()-1))N<<=1;arbitrary_module_polyf(1,mod),g(N,mod),h(*this),f2(1,mod);f[0]=qpow(data[0],mod-2,mod),h.resize(N),f2[0]=2;for(intw=2;w<N;w<<=1){g.resize(w);for(inti=0;i<w;++i)g[i]=h[i];f=f*(f*g-f2)*(mod-1);f.resize(w);}f.resize(size());returnf;}arbitrary_module_polyarbitrary_module_poly::inv(constint&h)const{arbitrary_module_polyf(*this);f.resize(h);returnf.inv();}arbitrary_module_polyarbitrary_module_poly::operator/(constint&h)const{intinv=qpow(h,mod-2,mod);std::vector<int>f(this->data);for(inti=0;i<(int)f.size();++i)f[i]=(ll)f[i]*inv%mod;returnarbitrary_module_poly(f,mod);}arbitrary_module_polyarbitrary_module_poly::operator/=(constint&h){intinv=qpow(h,mod-2,mod);for(inti=0;i<(int)size();++i)data[i]=(ll)data[i]*inv%mod;return*this;}arbitrary_module_polyarbitrary_module_poly::operator==(constarbitrary_module_poly&h)const{if(size()!=h.size()||mod!=h.mod)return0;for(inti=0;i<(int)size();++i)if(data[i]!=h[i])return0;return1;}arbitrary_module_polyarbitrary_module_poly::operator!=(constarbitrary_module_poly&h)const{if(size()!=h.size()||mod!=h.mod)return1;for(inti=0;i<(int)size();++i)if(data[i]!=h[i])return1;return0;}arbitrary_module_polysqrt(constarbitrary_module_poly&h){intN=1;while(N<(int)(h.size()+h.size()-1))N<<=1;arbitrary_module_polyf(1,mod),g(N,mod),d(h);f[0]=modsqrt(h[0],mod),d.resize(N);for(intw=2;w<N;w<<=1){g.resize(w);for(inti=0;i<w;++i)g[i]=d[i];f=(f+f.inv(w)*g)/2;f.resize(w);}f.resize(h.size());returnf;}arbitrary_module_polylog(constarbitrary_module_poly&h){arbitrary_module_polyf(h);for(inti=1;i<(int)f.size();++i)f[i-1]=(ll)f[i]*i%f.mod;f[f.size()-1]=0,f=f*h.inv(),f.resize(h.size());for(inti=(int)f.size()-1;i>0;--i)f[i]=(ll)f[i-1]*qpow(i,f.mod-2,f.mod)%f.mod;f[0]=0;returnf;}usingm_poly=arbitrary_module_poly;}// namespace fstdlib#endif
Computing All Stirling Numbers of the Second Kind for a Given Column
"Column" here refers to the set of \(\begin{Bmatrix}i\\k\end{Bmatrix}\) for fixed \(k\) and varying \(i\). Computing all Stirling numbers in a column means finding, for \(i=0..n\), the number of ways to partition \(i\) distinct elements into \(k\) non-empty subsets.
Use exponential generating functions.
The number of ways to put \(i\) items into one box (non-empty) is \([i>0]\). Its exponential generating function is \(F(x)=\sum\limits_{i=1}^{+\infty}\dfrac{x^i}{i!} = \mathrm{e}^x-1\). From previous knowledge, \(F^k(x)\) is the exponential generating function for placing \(i\) labeled items into \(k\) labeled boxes. Dividing by \(k!\) gives the exponential generating function for placing \(i\) labeled items into \(k\) unlabeled boxes.
Thus, \(\begin{Bmatrix}i\\k\end{Bmatrix}=\dfrac{\left[\dfrac{x^i}{i!}\right]F^k(x)}{k!}\). Compute the polynomial power in \(O(n\log n)\).
Also, \(\exp F(x)=\sum\limits_{i=0}^{+\infty}\dfrac{F^i(x)}{i!}\) is the exponential generating function for placing \(i\) labeled items into any number of unlabeled boxes (the EXP operation removes box labels by dividing each term by \(i!\)). This is essentially the generating function for Bell numbers.
Note the distinction between "labeled" and "unlabeled" in this context.
Stirling numbers of the first kind (Stirling cycle numbers) \(\begin{bmatrix}n\\ k\end{bmatrix}\), also denoted as \(s(n,k)\), represent the number of ways to partition \(n\) distinct elements into \(k\) indistinguishable non-empty cycles.
A cycle is a circular permutation. We can write a cycle as \([A,B,C,D]\), and we consider \([A,B,C,D]=[B,C,D,A]=[C,D,A,B]=[D,A,B,C]\), i.e., cycles that can be rotated into each other are equivalent. Note that we do not consider cycles that can be obtained by reflection as equivalent, i.e., \([A,B,C,D]\neq[D,C,B,A]\).
This is actually the rising factorial power \(x^{\overline n}\). This can be computed by brute-force divide-and-conquer multiplication in \(O(n\log^2n)\), but using techniques related to rising powers allows \(O(n\log n)\) computation. See Polynomial Shift | Continuous Point-Value Shift for details.
Computing All Stirling Numbers of the First Kind for a Given Column
Following the approach for the second kind, we can use exponential generating functions. Note that because the recurrence depends on the row, we cannot use the recurrence to compute numbers in the same column.
Clearly, the exponential generating function for a single cycle is