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

第一类斯特林数

暑假里,小L到海边去玩,捡了 n n n个不同的贝壳。现在她想把这些贝壳串成 k k k个项链(项链是环形的)。她忽然很疑惑,这有多少种方案呢?
聪明的小L很快想到,假设 S 1 ( n , k ) S_1(n,k) S1(n,k) n n n个贝壳串成 k k k条项链的方案数,那么显然有 S 1 ( n , k ) = S 1 ( n − 1 , k − 1 ) + ( n − 1 ) S 1 ( n − 1 , k ) S_1(n,k)=S_1(n-1,k-1)+(n-1)S_1(n-1,k) S1(n,k)=S1(n1,k1)+(n1)S1(n1,k),即要么将第 n n n个贝壳单独串成一条项链,要么让前 n − 1 n-1 n1个贝壳已经串成 k k k条项链,然后考虑第 n n n个贝壳放到哪个贝壳的后面。
这个递推式还可以看作进行 n n n次操作,其中第 i i i次操作有 i − 1 i-1 i1种方案不取物品,有 1 1 1种方案取一个新物品,最后取得 k k k个物品的方案数。根据这个意义,小L写出了一个生成函数:
∏ i = 0 n − 1 ( x + i ) \prod_{i=0}^{n-1}(x+i) i=0n1(x+i)
这个生成函数的 k k k次项系数就是 S 1 ( n , k ) S_1(n,k) S1(n,k)。可以用分治FFT。
通过查阅资料,小L还得知, ∏ i = 0 n − 1 ( x − i ) \prod_{i=0}^{n-1}(x-i) i=0n1(xi)这个多项式的每一项系数就是有符号第一类斯特林数,它们的递推式为 S 1 ( n , k ) = S 1 ( n − 1 , k − 1 ) − ( n − 1 ) S 1 ( n − 1 , k ) S_1(n,k)=S_1(n-1,k-1)-(n-1)S_1(n-1,k) S1(n,k)=S1(n1,k1)(n1)S1(n1,k)

例题codeforces960G
f ( i , j ) f(i,j) f(i,j)表示 i i i个数的排列,存在 j j j个数,在它们前面没有比它们大的数。
考虑最小的数放在哪,可以得到递推式: f ( i , j ) = f ( i − 1 , j − 1 ) + ( i − 1 ) f ( i − 1 , j ) f(i,j)=f(i-1,j-1)+(i-1)f(i-1,j) f(i,j)=f(i1,j1)+(i1)f(i1,j),就是第一类斯特林数。
因为 n n n的前面和后面都没有比它更大的数,所以题目要求的 a a a个数一定在 n n n前面, b b b个数一定在 n n n后面,枚举 n n n所在的位置,答案就是: ∑ i = 1 n S 1 ( i − 1 , a − 1 ) S 1 ( n − i , b − 1 ) C n − 1 i − 1 \sum_{i=1}^n S_1(i-1,a-1)S_1(n-i,b-1)C_{n-1}^{i-1} i=1nS1(i1,a1)S1(ni,b1)Cn1i1
考虑一些前面无比其大数的数,假设它们所在的位置为 p 1 , p 2 . . . p k p_1,p_2...p_k p1,p2...pk,把 [ p i , p i + 1 − 1 ] [p_i,p_{i+1}-1] [pi,pi+11]看作是一整块,那么我们可以先产生 a + b − 2 a+b-2 a+b2块,然后从其中选择 b − 1 b-1 b1块,将它们块内翻转,然后丢到 n n n的后面。所以答案就是:
S 1 ( n − 1 , a + b − 2 ) C a + b − 2 b − 1 S_1(n-1,a+b-2)C_{a+b-2}^{b-1} S1(n1,a+b2)Ca+b2b1

#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 n n条项链,项链各不相同。她准备了 k k k个一模一样的礼盒,她准备将项链装进礼盒里且不让任何礼盒是空的。现在她又想知道有多少种方案了。
通过考虑第 n n n条项链是单独放一个礼盒还是跟其他项链挤同一个礼盒,小L很快写出了递推式: S 2 ( n , k ) = S 2 ( n − 1 , k − 1 ) + k S 2 ( n − 1 , k ) S_2(n,k)=S_2(n-1,k-1)+kS_2(n-1,k) S2(n,k)=S2(n1,k1)+kS2(n1,k)
虽然不能生成函数做了,但是可以利用容斥原理,考虑空几个盒和盒子无序,得到一个新式子:
S 2 ( n , k ) = 1 k ! ∑ i = 0 k ( − 1 ) i C k i ( k − i ) n S_2(n,k)=\frac{1}{k!}\sum_{i=0}^k (-1)^i C_k^i (k-i)^n S2(n,k)=k!1i=0k(1)iCki(ki)n
这个可以写成卷积形式,用FFT O ( n l o g n ) O(nlogn) O(nlogn)求出。

例题:log2058/洛谷P4091
原式= ∑ j = 0 n 2 j j ! ∑ i = 0 n S 2 ( i , j ) \sum_{j=0}^n2^j j! \sum_{i=0}^n S_2(i,j) j=0n2jj!i=0nS2(i,j)
将第二类斯特林数的通项公式代入得:
∑ j = 0 n 2 j j ! ∑ i = 0 n ∑ k = 0 j ( − 1 ) k k ! ( j − k ) i ( j − k ) ! \sum_{j=0}^n 2^j j! \sum_{i=0}^n \sum_{k=0}^j \frac{(-1)^k}{k!} \frac{(j-k)^i}{(j-k)!} j=0n2jj!i=0nk=0jk!(1)k(jk)!(jk)i
发现后面的那个东西很像卷积,于是我们就让它更像卷积一点:
∑ j = 0 n 2 j j ! ∑ k = 0 j ( − 1 ) k k ! ∑ i = 0 n ( j − k ) i ( j − k ) ! \sum_{j=0}^n 2^j j! \sum_{k=0}^j \frac{(-1)^k}{k!} \frac{\sum_{i=0}^n(j-k)^i}{(j-k)!} j=0n2jj!k=0jk!(1)k(jk)!i=0n(jk)i
对了, ∑ i = 0 n t i = t n + 1 − 1 t − 1 \sum_{i=0}^nt^i=\frac{t^{n+1}-1}{t-1} i=0nti=t1tn+11,所以函数 f ( t ) = ( − 1 ) t t ! f(t)= \frac{(-1)^t}{t!} f(t)=t!(1)t g ( t ) = ∑ i = 0 n t i t ! g(t)=\frac{\sum_{i=0}^nt^i}{t!} g(t)=t!i=0nti都还挺好求的,那么答案就是:
∑ j = 0 n 2 j j ! ( f ∗ g ) ( j ) \sum_{j=0}^n 2^j j! (f*g)(j) j=0n2jj!(fg)(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;
}
  • 5
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值