斯特林数应用

前置知识

P6620 [省选联考 2020 A 卷] 组合数问题

题目大意

\(\left(\sum_{k=0}^{n}f(k)\times x^k\times \binom{n}{k}\right)\bmod p\)

分析

把多项式的每一项提出来并用第二类斯特林数展开,

\(\begin{aligned} \sum_{k=0}^n\sum_{i=0}^ma_ik^ix^k\binom{n}{k}&=\sum_{k=0}^n \sum_{i=0}^m a_ix^k\binom{n}{k} \sum_{j=0}^i \binom{k}{j}j! S(i,j)\\ &=\sum_{i=0}^m a_i \sum_{j=0}^i S(i,j)j! \sum_{k=0}^n x^k \binom{n}{k}\binom{k}{j} \end{aligned}\)

前面已经有一个 \(m^2\) 的枚举,所以后面的部分需要快速计算,

但是最后的组合数里有一个 \(j\) 就比较麻烦,

考虑后面两个组合数的实际意义,从 \(n\) 个元素中选出 \(k\) 个元素,再从 \(k\) 个元素中选出 \(j\) 个元素,实际上就相当于从 \(n\) 个元素中选出 \(j\) 个元素,在从剩下的 \(n-j\) 个元素中选出 \(k-j\) 个元素。

\(\begin{aligned} \sum_{i=0}^m a_i \sum_{j=0}^i S(i,j)j! \sum_{k=0}^n x^k \binom{n}{k}\binom{k}{j}&= \sum_{i=0}^m a_i \sum_{j=0}^i S(i,j)j! \sum_{k=0}^n x^k \binom{n}{j}\binom{n-j}{k-j} \\ &=\sum_{i=0}^m a_i \sum_{j=0}^i S(i,j)j!\binom{n}{j} \sum_{k=j}^n x^k \binom{n-j}{k-j}\\ &=\sum_{i=0}^m a_i \sum_{j=0}^i S(i,j)\frac{n!}{(n-j)!} x^j\sum_{k=0}^{n-j} x^k \binom{n-j}{k} \\&= \sum_{i=0}^m a_i \sum_{j=0}^i S(i,j)\frac{n!}{(n-j)!} x^j(x+1)^{n-j} \end{aligned}\)

总复杂度 \(m^2 logn\)

代码

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
#include<vector>
#define rg register
inline int read(){
	rg int x=0,fh=1;
	rg char ch=getchar();
	while(ch<'0' || ch>'9'){
		if(ch=='-') fh=-1;
		ch=getchar();
	}
	while(ch>='0' && ch<='9'){
		x=(x<<1)+(x<<3)+(ch^48);
		ch=getchar();
	}
	return x*fh;
}
const int maxn=5e3+5;
int n,m,ans,mod,a[maxn],x,f[maxn][maxn];
inline int addmod(rg int now1,rg int now2){
	return now1+=now2,now1>=mod?now1-mod:now1;
}
inline int delmod(rg int now1,rg int now2){
	return now1-=now2,now1<0?now1+mod:now1;
}
inline int mulmod(rg long long now1,rg int now2){
	return now1*=now2,now1>=mod?now1%mod:now1;
}
void pre(){
	f[0][0]=1;
	for(rg int i=1;i<=m;i++){
		for(rg int j=1;j<=m;j++){
			f[i][j]=addmod(f[i-1][j-1],mulmod(f[i-1][j],j));
		}
	}
}
int ksm(rg int ds,rg int zs){
	rg int nans=1;
	while(zs){
		if(zs&1) nans=mulmod(nans,ds);
		ds=mulmod(ds,ds);
		zs>>=1;
	}
	return nans;
}
int main(){
	n=read(),x=read(),mod=read(),m=read();
	pre();
	for(rg int i=0;i<=m;i++) a[i]=read();
	for(rg int i=0;i<=m;i++){
		rg int tmp=0,now=1;
		for(rg int j=0;j<=i;j++){
			tmp=addmod(tmp,mulmod(f[i][j],mulmod(now,mulmod(ksm(x,j),ksm(x+1,n-j)))));
			now=mulmod(now,n-j);
		}
		ans=addmod(ans,mulmod(a[i],tmp));
	}
	printf("%d\n",ans);
	return 0;
}

CF715E Complete the Permutations

题目大意

给定两个排列 \(p,q\),但是其中有些位置未知,用 \(0\) 表示。

现在让你补全两个排列,定义两个排列 \(p,q\) 之间的距离为每次选择 \(p\) 中两个元素交换,使其变成 \(q\) 的最小次数。

现在你需求出对于 \(i \in [0,n-1]\) 求出补全后相似度为 \(i\) 的方案数。

分析

如果 \(p,q\) 中的元素是已知的,那么我们只需要对于每一个 \(i\)\(p_i\)\(q_i\)连边,假设一共形成了 \(cnt\) 个环,那么最终的答案就是 \(n-cnt\)

现在元素是不确定的,仍然按照上面的方式连边,那么除了环以外最终会形成四种链\((0,x)(x,0)(0,0)(x,y)\)

设这些链分别有有 \(c_{01},c_{10},c_{00},c_{11}\) 个。

对于最后一种链因为 \(x\)\(y\) 只出现了一次,所以可以把 \(x\)\(y\) 看成同一个数,忽略这一条链,答案是不影响的。

对于第一种链,设 \(f[i]\) 为用这些 \((0,x)\) 链恰好形成 \(i\) 个环的方案数。

直接求不好求,考虑使用二项式反演,设 \(g[i]\) 为用这些 \((0,x)\) 链至少形成 \(i\) 个环的方案数。

那么 \(g[k]=\sum_{i=k}^{c_{01}} \binom{c_{01}}{i}s(i,j)(c_{00}+c_{01}-i)^{\underline{c_{01}-i}}\)

含义就是选出 \(i\) 条链构成 \(k\) 个环,这 \(k\) 个环只由 \((0,x)\) 链构成。

剩下的 \((0,x)\) 随意组合,可以和 \((0,x)\) 相接,也可以和 \((0,0)\) 相接,不用考虑是不是形成环。

如果和 \((0,x)\) 相接,最终得到的还是 \((0,x)\),如果和 \((0,0)\) 相接,得到的是 \((0,0)\),但是 \((0,0)\) 的数量加一减一并没有受到影响。

无论怎么接 \((0,0)\) 的数量都不变。

反演一下就能得到 \(f\) 数组的答案。

对于第二种链同理。

对于第三种链,设 \(r[i]\) 为用这些 \((0,0)\) 链恰好形成 \(i\) 个环的方案数,

那么 \(r[i]=s(c_{00},i) c_{00}!\)

乘上阶乘的含义是边可以任意排列。

最终的答案就是对这三个数组跑一遍背包,直接跑是三次方的,可以先跑前两个再跑最后一个。

代码

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
#include<vector>
#define rg register
inline int read(){
	rg int x=0,fh=1;
	rg char ch=getchar();
	while(ch<'0' || ch>'9'){
		if(ch=='-') fh=-1;
		ch=getchar();
	}
	while(ch>='0' && ch<='9'){
		x=(x<<1)+(x<<3)+(ch^48);
		ch=getchar();
	}
	return x*fh;
}
const int maxn=2e3+5,mod=998244353;
inline int addmod(rg int now1,rg int now2){
	return now1+=now2,now1>=mod?now1-mod:now1;
}
inline int delmod(rg int now1,rg int now2){
	return now1-=now2,now1<0?now1+mod:now1;
}
inline int mulmod(rg long long now1,rg int now2){
	return now1*=now2,now1>=mod?now1%mod:now1;
}
int n,a[maxn],b[maxn],C[maxn][maxn],s[maxn][maxn],A[maxn][maxn],jc[maxn],in[maxn],out[maxn],c00,c10,c01,cir;
bool vis[maxn];
void pre(){
	for(rg int i=1;i<=n;i++) in[i]=out[i]=-1;
	for(rg int i=1;i<=n;i++) out[a[i]]=b[i],in[b[i]]=a[i];
	for(rg int i=1;i<=n;i++){
		if(out[i]!=-1 && in[i]==-1){
			rg int now=i;
			while(now && out[now]!=-1) now=out[now];
			if(!now) c10++;
		}
	}
	for(rg int i=1;i<=n;i++){
		if(in[i]!=-1 && out[i]==-1){
			rg int now=i;
			while(now && in[now]!=-1) now=in[now];
			if(!now) c01++;
		}
	}
	for(rg int i=1;i<=n;i++) if(a[i]==0 && b[i]==0) c00++;
	for(rg int i=1;i<=n;i++){
		if(in[i]==0){
			rg int now=i;
			while(now && out[now]!=-1) now=out[now];
			if(!now) c00++;
		}
	}
	for(rg int i=1;i<=n;i++){
		if(out[i]>0 && !vis[i]){
			rg int now=out[i];
			vis[now]=1;
			while(now!=i && now && out[now]) now=out[now],vis[now]=1;
			if(now==i) cir++;
		}
	}
}
int f1[maxn],f2[maxn],g1[maxn],g2[maxn],f3[maxn],ans[maxn],tmp[maxn];
int main(){
	n=read();
	for(rg int i=1;i<=n;i++) a[i]=read();
	for(rg int i=1;i<=n;i++) b[i]=read();
	pre();
	s[0][0]=1;
	for(rg int i=1;i<maxn;i++){
		for(rg int j=1;j<maxn;j++){
			s[i][j]=addmod(s[i-1][j-1],mulmod(i-1,s[i-1][j]));
		}
	}
	C[0][0]=1;
	for(rg int i=1;i<maxn;i++) C[i][0]=1;
	for(rg int i=1;i<maxn;i++){
		for(rg int j=1;j<maxn;j++){
			C[i][j]=addmod(C[i-1][j],C[i-1][j-1]);
		}
	}
	jc[0]=1;
	for(rg int i=1;i<maxn;i++) jc[i]=mulmod(jc[i-1],i);
	for(rg int i=0;i<maxn;i++){
		for(rg int j=0;j<maxn;j++){
			A[i][j]=mulmod(C[i][j],jc[j]);
		}
	}
	for(rg int i=0;i<=c01;i++){
		for(rg int j=i;j<=c01;j++){
			g1[i]=addmod(g1[i],mulmod(C[c01][j],mulmod(s[j][i],A[c00+c01-j][c01-j])));
		}
	}
	for(rg int i=0;i<=c10;i++){
		for(rg int j=i;j<=c10;j++){
			g2[i]=addmod(g2[i],mulmod(C[c10][j],mulmod(s[j][i],A[c00+c10-j][c10-j])));
		}
	}
	for(rg int i=0;i<=c01;i++){
		for(rg int j=i;j<=c01;j++){
			if((j-i)&1) f1[i]=delmod(f1[i],mulmod(C[j][i],g1[j]));
			else f1[i]=addmod(f1[i],mulmod(C[j][i],g1[j]));
		}
	}
	for(rg int i=0;i<=c10;i++){
		for(rg int j=i;j<=c10;j++){
			if((j-i)&1) f2[i]=delmod(f2[i],mulmod(C[j][i],g2[j]));
			else f2[i]=addmod(f2[i],mulmod(C[j][i],g2[j]));
		}
	}
	for(rg int i=0;i<=c00;i++){
		f3[i]=mulmod(s[c00][i],jc[c00]);
	}
	for(rg int i=0;i<=c01;i++){
		for(rg int j=0;j<=c10;j++){
			tmp[i+j]=addmod(tmp[i+j],mulmod(f1[i],f2[j]));
		}
	}
	for(rg int i=0;i<=c01+c10;i++){
		for(rg int j=0;j<=c00;j++){
			ans[i+j]=addmod(ans[i+j],mulmod(tmp[i],f3[j]));
		}
	}
	for(rg int i=0;i<n;i++){
		if(n-i<cir) printf("0 ");
		else printf("%d ",ans[n-i-cir]);
	}
	printf("\n");
	return 0;
}

P4827 [国家集训队] Crash 的文明世界

题目大意

给定一颗树,设 \(S(i) = \sum_{j = 1}^{n}{\rm dist}(i, j) ^ k\)

对于 \(1 \sim n\) 中的每一个 \(i\),求出 \(S(i)\) 的值。

分析

如果没有 \(k\) 次方就是换根 \(dp\) 的模板题。

有了 \(k\) 次方之后,我们要做的就是怎么快速由 \(\sum_{i=1}^n dis(i,x)^k\) 转移到 \(\sum_{i=1}^n (dis(i,x)+1)^k\)

对于后面的部分用二项式定理展开:

\(\sum_{i=1}^n (dis(i,x)+1)^k=\sum_{i=1}^n \sum_{j=0}^k dis(i,x)^j \binom{k}{j}=\sum_{j=0}^k \binom{k}{j} \sum_{i=1}^ndis(i,x)^j\)

记录 \(dis^0\)\(dis^k\) 的和,先求出以 \(1\) 为根的答案,再用换根 \(dp\) 求出以其它节点为根的答案,就可以做到 \(nk^2\) 的复杂度,能拿到 \(50\) 分。

更快速的做法是用第二类斯特林数进行展开,

\(\begin{aligned} ans_x&=\sum_{i=1}^ndis(i,x)^k \\ &=\sum_{i=1}^n\sum_{j=0}^kS(k,j)C_{dis(i,x)}^jj! \\ &=\sum\limits_{j=0}^kS(k,j)j!\sum\limits_{i=1}^nC_{dis(i,x)}^j \\ &=\sum\limits_{j=0}^kS(k,j)j!\sum\limits_{i=1}^n(C_{dis(i,x)-1}^j+C_{dis(i,x)-1}^{j-1}) \end{aligned}\)

\(f[x][j]\) 表示 \(x\) 的子树内关于 \(C_{dis(i,x)}^j\) 的答案,

那么 \(f[x][j]=f[son][j]+f[son][j-1]\),

可以做到 \(nk\) 的复杂度。

代码

#include<cstdio>
#include<cmath>
#include<cstring>
#include<algorithm>
#include<iostream>
#include<vector>
#include<cstdlib>
#include<algorithm>
#define rg register
inline int read(){
	rg int x=0,fh=1;
	rg char ch=getchar();
	while(ch<'0' || ch>'9'){
		if(ch=='-') fh=-1;
		ch=getchar();
	}
	while(ch>='0' && ch<='9'){
		x=(x<<1)+(x<<3)+(ch^48);
		ch=getchar();
	}
	return x*fh;
}
const int maxn=1e5+5,maxm=155,mod=10007;
int h[maxn],tot=1,n,k;
struct asd{
	int to,nxt;
}b[maxn];
void ad(rg int aa,rg int bb){
	b[tot].to=bb;
	b[tot].nxt=h[aa];
	h[aa]=tot++;
}
int mi[maxn],c[maxm][maxm],f[maxn][maxm],g[maxn][maxm],s[maxm][maxm],jc[maxn];
void dfs1(rg int now,rg int lat){
	f[now][0]=1;
	for(rg int i=h[now];i!=-1;i=b[i].nxt){
		rg int u=b[i].to;
		if(u==lat) continue;
		dfs1(u,now);
		for(rg int j=1;j<=k;j++){
			f[now][j]+=f[u][j-1]+f[u][j];
			f[now][j]%=mod;
		}
		f[now][0]+=f[u][0];
		f[now][0]%=mod;
	}
}
int tmp1[maxm],tmp2[maxm],ans;
void dfs2(rg int now,rg int lat){
	if(now==1){
		for(rg int i=0;i<=k;i++) g[now][i]=f[now][i];
	}
	for(rg int i=h[now];i!=-1;i=b[i].nxt){
		rg int u=b[i].to;
		if(u==lat) continue;
		for(rg int j=1;j<=k;j++){
			tmp2[j]=f[u][j]+f[u][j-1];
			tmp2[j]%=mod;
		}
		tmp2[0]=f[u][0];
		for(rg int j=0;j<=k;j++) tmp1[j]=(g[now][j]-tmp2[j]+mod)%mod;
		for(rg int j=1;j<=k;j++){
			tmp2[j]=tmp1[j]+tmp1[j-1];
			tmp2[j]%=mod;
		}
		tmp2[0]=tmp1[0];
		for(rg int j=0;j<=k;j++){
			g[u][j]=f[u][j]+tmp2[j];
			g[u][j]%=mod;
		}
		dfs2(u,now);
	}
}
int main(){
	memset(h,-1,sizeof(h));
	n=read(),k=read();
	rg int aa,bb;
	for(rg int i=1;i<n;i++){
		aa=read(),bb=read();
		ad(aa,bb),ad(bb,aa);
	}
	for(rg int i=0;i<=k;i++) c[i][0]=1;
	for(rg int i=1;i<=k;i++){
		for(rg int j=1;j<=k;j++){
			c[i][j]=(c[i-1][j]+c[i-1][j-1])%mod;
		}
	}
	jc[0]=1;
	for(rg int i=1;i<=n;i++) jc[i]=jc[i-1]*i%mod;
	s[0][0]=1;
	for(rg int i=1;i<=k;i++){
		for(rg int j=1;j<=i;j++){
			s[i][j]=s[i-1][j-1]+s[i-1][j]*j;
			s[i][j]%=mod;
		}
	}
	dfs1(1,0);
	dfs2(1,0);
	for(rg int i=1;i<=n;i++){
		ans=0;
		for(rg int j=0;j<=k;j++){
			ans+=s[k][j]*jc[j]%mod*g[i][j]%mod;
			ans%=mod;
		}
		printf("%d\n",ans);
	}
	return 0;
}

P4609 [FJOI2016]建筑师

题目大意

对于一个 \(1,2,\ldots,n\) 的排列,设有 \(A\) 个数的左边都比它小, \(B\) 个数的右边都比它小。已知 \(n,A,B\),求满足的排列个数。

分析

\(f[i][j]\) 为前 \(i\) 个建筑从左到右有 \(j\) 个建筑能看到的方案,

强制让每一次加的建筑高度是最小的,

那么 \(f[i][j]=f[i-1][j-1]+(i-1)f[i-1][j]\)

含义是只有加在最前面的位置才能作出贡献,否则插在其它 \(i-1\) 个建筑的后面都不会作出贡献。

这个转移和第一类斯特林数是一样的,

那么答案就是:

\(ans=\sum_{i=1}^{n}s(i-1,A-1)s(n-i,B-1)\binom{n-1}{i-1}\)

考虑组合意义,

\(n − 1\) 个元素任意涂成黑白色,将黑色元素塞入 \(A − 1\) 个无区别的环,将
白色元素塞入 \(B-1\) 个无区别的环,

等价于:把 \(n − 1\) 个元素塞入 \(A − 1 + B − 1\) 个无区别的环,将其中 \(A − 1\) 个环涂成白色, \(B − 1\) 个涂成黑色。

\(ans=s(n-1,A+B-2)\binom{A+B-2}{A-1}\)

斯特林数直接递推求即可。

代码

#include<cstdio>
#include<cstring>
#include<vector>
#include<iostream>
#include<algorithm>
#define rg register
inline int read(){
	rg int x=0,fh=1;
	rg char ch=getchar();
	while(ch<'0' || ch>'9'){
		if(ch=='-') fh=-1;
		ch=getchar();
	}
	while(ch>='0' && ch<='9'){
		x=(x<<1)+(x<<3)+(ch^48);
		ch=getchar();
	}
	return x*fh;
}
const int maxn=5e4+5,maxm=1e6+5,mod=1e9+7;
inline int addmod(rg int now1,rg int now2){
	return now1+=now2,now1>=mod?now1-mod:now1;
}
inline int delmod(rg int now1,rg int now2){
	return now1-=now2,now1<0?now1+mod:now1;
}
inline int mulmod(rg long long now1,rg int now2){
	return now1*=now2,now1>=mod?now1%mod:now1;
}
int f[maxn][205],ny[maxm],jc[maxm],jcc[maxm];
void pre(){
	f[0][0]=1;
	for(rg int i=1;i<maxn;i++){
		for(rg int j=1;j<=200;j++){
			f[i][j]=addmod(f[i-1][j-1],mulmod(f[i-1][j],i-1));
		}
	}
	ny[1]=1;
	for(rg int i=2;i<maxn;i++) ny[i]=mulmod(mod-mod/i,ny[mod%i]);
	jc[0]=jcc[0]=1;
	for(rg int i=1;i<maxn;i++) jc[i]=mulmod(jc[i-1],i),jcc[i]=mulmod(jcc[i-1],ny[i]);
}
int getC(rg int nn,rg int mm){
	if(nn<mm || nn<0 || mm<0) return 0;
	return mulmod(jc[nn],mulmod(jcc[mm],jcc[nn-mm]));
}
int t,n,a,b,ans;
int main(){
	pre();
	t=read();
	while(t--){
		n=read(),a=read(),b=read();
		if(n==1) ans=(a==1 && b==1);
		else if(a+b<2 || a<0 || b<0) ans=0;
		else ans=mulmod(f[n-1][a+b-2],getC(a+b-2,a-1));
		printf("%d\n",ans);
	}
	return 0;
}
posted @ 2021-03-12 17:03  liuchanglc  阅读(143)  评论(0编辑  收藏  举报