【CodeChef-EASYEX】Easy exam(斯特林数)(FFT)

传送门


题解:

x i , j x_{i,j} xi,j表示第 i i i次是否扔出了 j j j,则我们要求的就是 ∏ i = 1 L ( ∑ j = 1 n x j , i ) F \prod_{i=1}^L(\sum_{j=1}^nx_{j,i})^F i=1L(j=1nxj,i)F

的期望。

考虑这个式子完全展开后的某一项,假设有 z z z x j , i x_{j,i} xj,i,则系数为 S F , z z ! S_{F,z}z! SF,zz!,所有的有 z z z个项的系数和为 S F , z n z ‾ S_{F,z}n^{\underline{z}} SF,znz

然而考虑到选择 x j , i 1 , x j , i 2 x_{j,i_1},x_{j,i_2} xj,i1,xj,i2,是不可能的,贡献为 0 0 0

f i , j f_{i,j} fi,j表示在前 i i i ( ∑ x ) f (∑x)f (x)f中,选出 j j j个不同的 x x x的方案数(不考虑选的顺序)。

发现转移对应一个子集划分,可以直接用卷积算出来。

支持一个多项式快速幂就行了。


代码:

#include<bits/stdc++.h>
#define ll long long
#define re register
#define cs const

using std::cerr;
using std::cout;

cs int mod=2003;
inline int power(int a,int b){
	int res=1;for(;b;b>>=1,a=(ll)a*a%mod)(b&1)&&(res=(ll)a*res%mod);
	return res;
}

cs double PI=acos(-1);
struct cp{
	double x,y;
	cp(){}
	cp(double _x,double _y):x(_x),y(_y){}
	friend cp operator+(cs cp &a,cs cp &b){return cp(a.x+b.x,a.y+b.y);}
	friend cp operator-(cs cp &a,cs cp &b){return cp(a.x-b.x,a.y-b.y);}
	friend cp operator*(cs cp &a,cs cp &b){return cp(a.x*b.x-a.y*b.y,a.x*b.y+b.x*a.y);}
	friend cp operator/(cs cp &a,int n){return cp(a.x/n,a.y/n);}
};

cs int SIZE=1<<15|1;
int r[SIZE];
inline void FFT(cp *A,int len,int typ){
	for(int re i=0;i<len;++i)if(i<r[i])std::swap(A[i],A[r[i]]);
	for(int re i=1;i<len;i<<=1){
		cp wn=cp(cos(PI/i),typ*sin(PI/i));
		for(int re j=0;j<len;j+=i<<1){
			cp w(1,0);
			for(int re k=0;k<i;++k,w=w*wn){
				cp x=A[j+k],y=A[j+k+i]*w;
				A[j+k]=x+y;
				A[j+k+i]=x-y;
			}
		}
	}
	if(typ==-1)for(int re i=0;i<len;++i)A[i]=A[i]/len;
}

inline int mul(int *a,int al,int *b,int bl,int *c){
	static cp A[SIZE],B[SIZE];
	int l=1,cl=al+bl;
	while(l<=cl)l<<=1;
	for(int re i=0;i<l;++i)r[i]=r[i>>1]>>1|((i&1)?l>>1:0);
	for(int re i=0;i<l;++i)A[i]=B[i]=cp(0,0);
	for(int re i=0;i<=al;++i)A[i].x=a[i];
	for(int re i=0;i<=bl;++i)B[i].x=b[i];
	FFT(A,l,1),FFT(B,l,1);
	for(int re i=0;i<l;++i)A[i]=A[i]*B[i];
	FFT(A,l,-1);
	cl=std::min(cl,mod+5);
	for(int re i=0;i<=cl;++i)c[i]=((ll)floor(A[i].x+0.5))%mod;
	return cl;
}

cs int N=1e3+7;
int S[N][N];
int a[SIZE],b[SIZE];

inline void solve(){
	int n,k,l,f;scanf("%d%d%d%d",&n,&k,&l,&f);
	if(k%mod==0){
		puts("0");
		return ;
	}
	for(int re i=0;i<=f;++i)a[i]=b[i]=S[f][i];
	int al=f,bl=f;
	for(int re t=l-1;t;){
		if(t&1)bl=mul(a,al,b,bl,b);
		if(t>>=1)al=mul(a,al,a,al,a);
	}
	int A=1,ik=power(k,mod-2),ans=0;
	for(int re i=1;i<=std::min(n,l);++i)
	A=(ll)A*(n-i+1)%mod*ik%mod;
	for(int re i=l;i<=std::min(n,l*f);++i){
		if(!A)break;
		ans=(ans+(ll)A*b[i])%mod;
		A=(ll)A*(n-i)%mod*ik%mod;
	}
	cout<<ans<<"\n";
}

signed main(){
#ifdef zxyoi
	freopen("easyex.in","r",stdin);
#endif
	S[0][0]=1;
	for(int re i=1;i<N;++i)
	for(int re j=1;j<=i;++j)S[i][j]=(S[i-1][j-1]+j*S[i-1][j])%mod;
	int T;scanf("%d",&T);
	while(T--)solve();
	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值