第一和第二类斯特林数小结

第一类斯特林数

暑假里,小L到海边去玩,捡了 n 个不同的贝壳。现在她想把这些贝壳串成 k 个项链(项链是环形的)。她忽然很疑惑,这有多少种方案呢?
聪明的小L很快想到,假设 S 1 ( n , k ) n 个贝壳串成 k 条项链的方案数,那么显然有 S 1 ( n , k ) = S 1 ( n 1 , k 1 ) + ( n 1 ) S 1 ( n 1 , k ) ,即要么将第 n 个贝壳单独串成一条项链,要么让前 n 1 个贝壳已经串成 k 条项链,然后考虑第 n 个贝壳放到哪个贝壳的后面。
这个递推式还可以看作进行 n 次操作,其中第 i 次操作有 i 1 种方案取一个新物品,有 1 种方案不取物品,最后取得 k 个物品的方案数。根据这个意义,小L写出了一个生成函数:

i = 0 n 1 ( x + i )

这个生成函数的 k 次项系数就是 S 1 ( n , k ) 。可以用分治FFT做到 O ( n l o g 2 n ) 的复杂度。
通过查阅资料,小L还得知, i = 0 n 1 ( x i ) 这个多项式的每一项系数就是有符号第一类斯特林数,它们的递推式为 S 1 ( n , k ) = S 1 ( n 1 , k 1 ) ( n 1 ) S 1 ( n 1 , k )

例题codeforces960G
f ( i , j ) 表示 i 个数的排列,存在 j 个数,在它们前面没有比它们大的数。
考虑最小的数放在哪,可以得到递推式: f ( i , j ) = f ( i 1 , j 1 ) + ( i 1 ) f ( i 1 , j ) ,就是第一类斯特林数。
因为 n 的前面和后面都没有比它更大的数,所以题目要求的 a 个数一定在 n 前面, b 个数一定在 n 后面,枚举 n 所在的位置,答案就是:

i = 1 n S 1 ( i 1 , a 1 ) S 1 ( n i , b 1 ) C n 1 i 1

考虑一些前面无比其大数的数,假设它们所在的位置为 p 1 , p 2 . . . p k ,把 [ p i , p i + 1 1 ] 看作是一整块,那么我们可以先产生 a + b 2 块,然后从其中选择 b 1 块,将它们块内翻转,然后丢到 n 的后面。所以答案就是:
S 1 ( n 1 , a + b 2 ) C a + b 2 b 1

#include<bits/stdc++.h>
using namespace std;
#define RI register int
const int N=200005,mod=998244353,G=3;
int n,A,B,ans,a[18][N],rev[N];
int ksm(int x,int y) {
    int re=1;
    for(RI i=y;i;i>>=1,x=1LL*x*x%mod) if(i&1) re=1LL*re*x%mod;
    return re;
}
void NTT(int *a,int n,int x) {
    for(RI i=0;i<n;++i) if(rev[i]>i) swap(a[i],a[rev[i]]);
    for(RI i=1;i<n;i<<=1) {
        int gn=ksm(G,(mod-1)/(i<<1));
        for(RI j=0;j<n;j+=(i<<1)) {
            int g=1,t1,t2;
            for(RI k=0;k<i;++k,g=1LL*g*gn%mod) {
                t1=a[j+k],t2=1LL*g*a[j+i+k]%mod;
                a[j+k]=(t1+t2)%mod,a[j+i+k]=(t1-t2+mod)%mod;
            }
        }
    }
    if(x==1) return;
    int inv=ksm(n,mod-2);reverse(a+1,a+n);//a+1!!!
    for(RI i=0;i<n;++i) a[i]=1LL*a[i]*inv%mod;
}
void work(int s,int t,int d) {
    if(s==t) {a[d][0]=s,a[d][1]=1;return;}
    int mid=(s+t)>>1,len=0,kn=1;
    work(s,mid,d+1);
    for(RI i=0;i<=mid-s+1;++i) a[d][i]=a[d+1][i];
    work(mid+1,t,d+1);
    while(kn<=t-s+1) kn<<=1,++len;
    for(RI i=0;i<kn;++i) rev[i]=(rev[i>>1]>>1)|((i&1)<<(len-1));
    for(RI i=mid-s+2;i<kn;++i) a[d][i]=0;
    for(RI i=t-mid+1;i<kn;++i) a[d+1][i]=0;
    NTT(a[d],kn,1),NTT(a[d+1],kn,1);
    for(RI i=0;i<kn;++i) a[d][i]=1LL*a[d][i]*a[d+1][i]%mod;
    NTT(a[d],kn,-1);
}
int C(int d,int u) {
    int k1=1,k2=1;
    for(RI i=d-u+1;i<=d;++i) k1=1LL*k1*i%mod;
    for(RI i=1;i<=u;++i) k2=1LL*k2*i%mod;
    return 1LL*k1*ksm(k2,mod-2)%mod;
}
int main()
{
    scanf("%d%d%d",&n,&A,&B);
    if(!A||!B||A+B-2>n-1) {puts("0");return 0;}
    if(n==1) {puts("1");return 0;}
    work(0,n-2,0);
    ans=1LL*a[0][A+B-2]*C(A+B-2,B-1)%mod;
    printf("%d\n",ans);
    return 0;
}

第二类斯特林数

现在,小L准备把这些项链送给她的朋友们。假设有 n 条项链,项链各不相同。她准备了 k 个一模一样的礼盒,她准备将项链装进礼盒里且不让任何礼盒是空的。现在她又想知道有多少种方案了。
通过考虑第 n 条项链是单独放一个礼盒还是跟其他项链挤同一个礼盒,小L很快写出了递推式:

S 2 ( n , k ) = S 2 ( n 1 , k 1 ) + k S 2 ( n 1 , k )

虽然不能生成函数做了,但是可以利用容斥原理,考虑空几个盒和盒子无序,得到一个新式子:
S 2 ( n , k ) = 1 k ! i = 0 k ( 1 ) i C k i ( k i ) n

这个可以写成卷积形式,用FFT O ( n l o g n ) 求出。

例题:log2058/洛谷P4091
原式= j = 0 n 2 j j ! i = 0 n S 2 ( i , j )
将第二类斯特林数的通项公式代入得:

j = 0 n 2 j j ! i = 0 n k = 0 j ( 1 ) k k ! ( j k ) i ( j k ) !

发现后面的那个东西很像卷积,于是我们就让它更像卷积一点:
j = 0 n 2 j j ! k = 0 j ( 1 ) k k ! i = 0 n ( j k ) i ( j k ) !

对了, i = 0 n t i = t n + 1 1 t 1 ,所以函数 f ( t ) = ( 1 ) t t ! g ( t ) = i = 0 n t i t ! 都还挺好求的,那么答案就是:
j = 0 n 2 j j ! ( f g ) ( j )

#include<bits/stdc++.h>
using namespace std;
#define RI register int
const int mod=998244353,G=3,N=262150;
int n,kn,len,ans;
int a[N],b[N],fac[N],ni[N],rev[N];
int ksm(int x,int y) {
    int re=1;
    for(;y;y>>=1,x=1LL*x*x%mod) if(y&1) re=1LL*re*x%mod;
    return re;
}
void NTT(int *a,int n,int x) {
    for(RI i=0;i<n;++i) if(rev[i]>i) swap(a[i],a[rev[i]]);
    for(RI i=1;i<n;i<<=1) {
        int gn=ksm(G,(mod-1)/(i<<1));
        for(RI j=0;j<n;j+=(i<<1)) {
            int g=1,t1,t2;
            for(RI k=0;k<i;++k,g=1LL*g*gn%mod) {
                t1=a[j+k],t2=1LL*g*a[j+i+k]%mod;
                a[j+k]=(t1+t2)%mod,a[j+i+k]=(t1-t2+mod)%mod;
            }
        }
    }
    if(x==1) return;
    int inv=ksm(n,mod-2);reverse(a+1,a+n);
    for(RI i=0;i<n;++i) a[i]=1LL*a[i]*inv%mod;
}
int main()
{
    scanf("%d",&n);
    fac[0]=1;for(RI i=1;i<=n;++i) fac[i]=1LL*fac[i-1]*i%mod;
    ni[n]=ksm(fac[n],mod-2);
    for(RI i=n-1;i>=0;--i) ni[i]=1LL*ni[i+1]*(i+1)%mod;
    for(RI i=0;i<=n;++i) {
        a[i]=1LL*(1-2*(i&1)+mod)%mod*ni[i]%mod;
        if(i!=1) b[i]=1LL*(ksm(i,n+1)-1+mod)%mod*ni[i]%mod*ksm(i-1+mod,mod-2)%mod;
        else b[i]=n+1;
    }
    kn=1;while(kn<=n+n) kn<<=1,++len;
    for(RI i=0;i<kn;++i) rev[i]=(rev[i>>1]>>1)|((i&1)<<(len-1));
    NTT(a,kn,1),NTT(b,kn,1);
    for(RI i=0;i<kn;++i) a[i]=1LL*a[i]*b[i]%mod;
    NTT(a,kn,-1);
    for(RI i=0,j=1;i<=n;++i,j=(j+j)%mod)
        ans=(ans+1LL*j*fac[i]%mod*a[i]%mod)%mod;
    printf("%d\n",ans);
    return 0;
}

猜你喜欢

转载自blog.csdn.net/litble/article/details/80882581