洛谷 P5396 第二类斯特林数·列 题解

题目传送门

题目大意: 给出 n , k n,k n,k,对于 i ∈ [ 1 , n ] i\in[1,n] i[1,n] 求出 { i k } \left\{ \begin{matrix}i\\k\end{matrix} \right\} {ik}

题解

做法1

按套路先列出答案的生成函数: F k ( x ) = ∑ i = 0 { i k } x i F_k(x)=\sum_{i=0}\left\{ \begin{matrix}i\\k\end{matrix} \right\}x^i Fk(x)=i=0{ik}xi

根据第二类斯特林数的递推式: S ( n , m ) = S ( n − 1 , m − 1 ) + m S ( n − 1 , m ) S(n,m)=S(n-1,m-1)+mS(n-1,m) S(n,m)=S(n1,m1)+mS(n1,m),考虑将其写成封闭形式:
F k = ∑ i = 0 n S ( i , k ) x i = ∑ i = 0 n ( S ( i − 1 , k − 1 ) + k S ( i − 1 , k ) ) x i F k = x F k − 1 + k x F k \begin{aligned} F_k&=\sum_{i=0}^n S(i,k) x^i=\sum_{i=0}^n(S(i-1,k-1)+kS(i-1,k))x^i\\ F_k&=xF_{k-1}+kxF_k\\ \end{aligned} FkFk=i=0nS(i,k)xi=i=0n(S(i1,k1)+kS(i1,k))xi=xFk1+kxFk

在往下推一步,就能得到:
F k = x 1 − k x F k − 1 = F 0 x k ( ∏ i = 0 k ( 1 − i x ) ) − 1 F_k=\frac x {1-kx} F_{k-1}=F_0 x^k (\prod_{i=0}^k (1-ix))^{-1} Fk=1kxxFk1=F0xk(i=0k(1ix))1

所以用分治 F F T FFT FFT 求出后面那个 ∏ \prod ,然后再求个逆,最后右移 k k k 位就是答案,时间复杂度 O ( n log ⁡ 2 n ) O(n\log^2 n) O(nlog2n),在洛谷开 O2 后是 814 m s 814ms 814ms

代码如下:

#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
using namespace std;
#define maxn 300010
#define mod 167772161
#define bin(x) (1<<(x))
#define MS(f,lg) memset(f,0,4<<lg)
#define CP(f,g,ln) memcpy(f,g,ln<<2)

int n,m,*F[maxn],G[maxn],t=0;
int ksm(int x,int y){int re=1;for(;(y&1?re=1ll*re*x%mod:0),y;y>>=1,x=1ll*x*x%mod);return re;}
int w[maxn],inv[maxn],log_2[maxn];void prep(int lg){int N=bin(lg);
	inv[1]=1;for(int i=2;i<=N;i++)inv[i]=1ll*(mod-mod/i)*inv[mod%i]%mod,log_2[i]=ceil(log2(i));
	for(int i=1,wn;i<N;i<<=1){
		w[i]=1;wn=ksm(3,(mod-1)/(i<<1));
		for(int j=1;j<i;j++)w[i+j]=1ll*w[i+j-1]*wn%mod;
	}
}
int limit,r[maxn];void work(int lg){for(int i=1;i<bin(lg);i++)r[i]=(r[i>>1]>>1)|((i&1)<<(lg-1));}
int add(int x){return x>=mod?x-mod:x;}
int dec(int x){return x<0?x+mod:x;}
void ntt(int *f,int lg,int type=0)
{
	limit=bin(lg);if(type)reverse(f+1,f+limit);
	for(int i=1;i<limit;i++)if(i<r[i])swap(f[i],f[r[i]]);
	for(int mid=1,t;mid<limit;mid<<=1)for(int j=0;j<limit;j+=(mid<<1))for(int i=0;i<mid;i++)
	{t=1ll*f[j+i+mid]*w[mid+i]%mod;f[j+i+mid]=dec(f[j+i]-t);f[j+i]=add(f[j+i]+t);}
	if(type)for(int i=0;i<limit;i++)f[i]=1ll*f[i]*inv[limit]%mod;
}
int A[maxn],B[maxn];
void getinv(int *f,int *g,int ln)
{
	if(ln==1){g[0]=ksm(f[0],mod-2);return;}getinv(f,g,(ln+1)>>1);
	int lg=log_2[ln*2-1];work(lg);MS(A,lg);MS(B,lg);CP(A,f,ln);CP(B,g,ln);
	ntt(A,lg);ntt(B,lg);for(int i=0;i<bin(lg);i++)B[i]=1ll*(2-1ll*A[i]*B[i]%mod+mod)*B[i]%mod;
	ntt(B,lg,1);for(int i=0;i<ln;i++)g[i]=B[i];
}
void solve(int id,int l,int r)
{
	if(id)F[id]=new int[r-l+2];if(l==r){F[id][0]=1;F[id][1]=mod-l;return;}
	int mid=l+r>>1;int ls=++t;solve(ls,l,mid);int rs=++t;solve(rs,mid+1,r);
	int lg=log_2[r-l+2];MS(A,lg);MS(B,lg);CP(A,F[ls],mid-l+2);CP(B,F[rs],r-(mid+1)+2);
	work(lg);ntt(A,lg);ntt(B,lg);for(int i=0;i<bin(lg);i++)A[i]=1ll*A[i]*B[i]%mod;
	ntt(A,lg,1);for(int i=0;i<r-l+2;i++)F[id][i]=A[i];
}

int main()
{
	scanf("%d %d",&n,&m);prep((int)ceil(log2(n<<1)));
	if(m>n){for(int i=0;i<=n;i++)printf("0 ");return 0;}
	F[0]=new int[n<<1];for(int i=0;i<(n<<1);i++)F[0][i]=0;
	solve(0,0,m);getinv(F[0],G,n-m+1);
	for(int i=0;i<m;i++)printf("0 ");
	for(int i=0;i<=n-m;i++)printf("%d ",G[i]);
}
做法2

这种做法更容易想,但是实现更长一些并且虽然理论时间复杂度更小,但是由于多项式快速幂奇大的常数跑得比上面的做法还慢。

考虑第二类斯特林数的意义,那么 F k ( x ) F_k(x) Fk(x) x i x^i xi 项的系数就代表:将 i i i 个小球放到 k k k 个盒子里,每个盒子都不为空的方案数。

现在盒子数是固定的,于是考虑往一个盒子里放球的生成函数: G ( x ) = x 1 ! + x 2 2 ! + x 3 3 ! + . . . G(x)=\dfrac x {1!}+\dfrac {x^2} {2!}+\dfrac {x^3} {3!}+... G(x)=1!x+2!x2+3!x3+...,发现就等于 e x − 1 e^x-1 ex1,因为盒子不能为空,所以没有 x 0 x^0 x0 这一项。

由于需要分 k k k 个盒子,所以 ( e x − 1 ) k k ! \dfrac {(e^x-1)^k} {k!} k!(ex1)k 就是答案,由于这里用的是 E G F EGF EGF,所以答案的每一项还要乘以 i ! i! i!

时间复杂度为 O ( n log ⁡ n ) O(n\log n) O(nlogn),在洛谷开 O2 后是 3.04 s 3.04s 3.04s……代码如下:

#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
using namespace std;
#define maxn 300010
#define mod 167772161
#define bin(x) (1<<(x))
#define MS(f,x) memset(f,0,4<<x)
#define CP(f,g,x) memcpy(f,g,x<<2)

int n,k,F[maxn],G[maxn];
int ksm(int x,int y){int re=1;for(;(y&1?re=1ll*re*x%mod:0),y;y>>=1,x=1ll*x*x%mod);return re;}
int w[maxn],inv[maxn],log_2[maxn];void prep(int lg){int N=bin(lg);
	inv[1]=1;for(int i=2;i<=N;i++)inv[i]=1ll*(mod-mod/i)*inv[mod%i]%mod,log_2[i]=ceil(log2(i));
	for(int i=1,wn;i<N;i<<=1){
		w[i]=1;wn=ksm(3,(mod-1)/(i<<1));
		for(int j=1;j<i;j++)w[i+j]=1ll*w[i+j-1]*wn%mod;
	}
}
int limit,r[maxn];void work(int lg){for(int i=1;i<bin(lg);i++)r[i]=(r[i>>1]>>1)|((i&1)<<(lg-1));}
int add(int x){return x>=mod?x-mod:x;}
int dec(int x){return x<0?x+mod:x;}
void ntt(int *f,int lg,int type=0)
{
	limit=bin(lg);if(type)reverse(f+1,f+limit);
	for(int i=1;i<limit;i++)if(i<r[i])swap(f[i],f[r[i]]);
	for(int mid=1,t;mid<limit;mid<<=1)for(int j=0;j<limit;j+=(mid<<1))for(int i=0;i<mid;i++)
	{t=1ll*f[j+i+mid]*w[mid+i]%mod;f[j+i+mid]=dec(f[j+i]-t);f[j+i]=add(f[j+i]+t);}
	if(type)for(int i=0;i<bin(lg);i++)f[i]=1ll*f[i]*inv[limit]%mod;
}
void NTT(int *f,int *g,int ln){
	int lg=log_2[ln<<1];work(lg);ntt(f,lg);ntt(g,lg);
	for(int i=0;i<bin(lg);i++)f[i]=1ll*f[i]*g[i]%mod;ntt(f,lg,1);
}
int A[maxn],B[maxn],C[maxn],D[maxn],E[maxn],H[maxn];
void getinv(int *f,int *g,int ln)
{
	if(ln==1){g[0]=ksm(f[0],mod-2);return;}getinv(f,g,(ln+1)>>1);
	int lg=log_2[ln<<1];work(lg);MS(A,lg);MS(B,lg);CP(A,f,ln);CP(B,g,ln);
	ntt(A,lg);ntt(B,lg);for(int i=0;i<bin(lg);i++)B[i]=1ll*(2-1ll*A[i]*B[i]%mod+mod)*B[i]%mod;
	ntt(B,lg,1);for(int i=0;i<ln;i++)g[i]=B[i];
}
void Dao(int *f,int *g,int ln){for(int i=0;i<ln-1;i++)g[i]=1ll*f[i+1]*(i+1)%mod;g[ln-1]=0;}
void Jifen(int *f,int *g,int ln){for(int i=ln-1;i>0;i--)g[i]=1ll*f[i-1]*inv[i]%mod;g[0]=0;}
void getln(int *f,int *g,int ln){
	MS(C,log_2[ln<<1]);MS(D,log_2[ln<<1]);
	Dao(f,C,ln);getinv(f,D,ln);NTT(C,D,ln);Jifen(C,g,ln);
}
void getexp(int *f,int *g,int ln){
	if(ln==1){g[0]=1;return;}getexp(f,g,(ln+1)>>1);
	int lg=log_2[ln<<1];MS(E,lg);getln(g,E,ln);
	for(int i=1;i<ln;i++)E[i]=(f[i]-E[i]+mod)%mod;E[0]++;NTT(g,E,ln);
}
void getksm(int *f,int *g,int ln){
	int st=1;for(int i=0;i<ln-st;i++)f[i]=f[i+st];
	getln(f,g,ln);for(int i=0;i<ln;i++)f[i]=1ll*g[i]*k%mod,g[i]=0;
	getexp(f,g,ln);st=k;for(int i=ln-1;i>=0;i--)g[i]=i<st?0:g[i-st];
}
int fac[maxn],inv_fac[maxn];
void work(){
	fac[0]=inv_fac[0]=1;for(int i=1;i<=maxn-10;i++)fac[i]=1ll*fac[i-1]*i%mod;
	inv_fac[maxn-10]=ksm(fac[maxn-10],mod-2);
	for(int i=maxn-11;i>=1;i--)inv_fac[i]=1ll*inv_fac[i+1]*(i+1)%mod;
}

int main()
{
	scanf("%d %d",&n,&k);prep((int)ceil(log2(n<<1)));work();
	for(int i=1;i<=n;i++)F[i]=inv_fac[i];
	getksm(F,G,n+1);for(int i=0;i<=n;i++)printf("%d ",1ll*G[i]*fac[i]%mod*inv_fac[k]%mod);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值