第一和第二类斯特林数的学习

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

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);
}

猜你喜欢

转载自www.cnblogs.com/clno1/p/10809678.html