【TJOI 2019】唱、跳、rap和篮球

传送门


problem

大中锋的学院要组织学生参观博物馆,要求学生们在博物馆中排成一队进行参观。
他的同学可以分为四类:一部分最喜欢唱、一部分最喜欢跳、一部分最喜欢 rap,还有一部分最喜欢篮球。

若队列中 k , k + 1 , k + 2 , k + 3 k,k+1,k+2,k+3 k,k+1,k+2,k+3 位置上的同学依次,最喜欢唱、最喜欢跳、最喜欢 rap、最喜欢篮球,他们就会聚在一起讨论蔡徐坤。
大中锋不希望这种事情发生,因为这会使得队伍显得很乱。

大中锋想知道有多少种排队的方法,不会有学生聚在一起讨论蔡徐坤。
两个学生队伍被认为是不同的,当且仅当两个队伍中至少有一个位置上的学生的喜好不同。
由于合法的队伍可能会有很多种,种类数对 998244353 998244353 998244353 取模。

学生中最喜欢唱的人数、最喜欢跳的人数、最喜欢 rap 的人数和最喜欢篮球的人数分别为 A , B , C , D A,B,C,D A,B,C,D

数据范围: n ≤ 1000 n≤1000 n1000 A , B , C , D ≤ 500 A,B,C,D≤500 A,B,C,D500


solution

为了方便,我们把要讨论蔡徐坤的段称为 “ “ cxk ” ”

那么一个比较显然的思路就是枚举 cxk 段的数量然后容斥。从 n n n 个数中选 i i icxk 段的方案数是 C n − 3 i i C_{n-3i}^{i} Cn3ii,因为每个 cxk 段占 4 4 4 个位置,那么段头只能在 n − 3 i n-3i n3i 个位置里面选,所以:

a n s = ∑ i = 0 min ⁡ ( a , b , c , d , ⌊ n 4 ⌋ ) ( − 1 ) i ( n − 3 i i ) f i ans=\sum_{i=0}^{\min(a,b,c,d,\lfloor\frac n 4\rfloor)}(-1)^i\binom {n-3i} i f_i ans=i=0min(a,b,c,d,4n)(1)i(in3i)fi

其中 f i f_i fi 表示在剩下的 n − 4 i n-4i n4i 个位置上选人的方案数。现在每个部分剩下的人分别有 A − i , B − i , C − i , D − i A-i,B-i,C-i,D-i Ai,Bi,Ci,Di 个。

这就相当于求有重复元素的排列

如果 A + B + C + D = n A+B+C+D=n A+B+C+D=n 的话,这一部分贡献应是 ( n − 4 i ) ! ( A − i ) ! ( B − i ) ! ( C − i ) ! ( D − i ) ! \frac{(n-4i)!}{(A-i)!(B-i)!(C-i)!(D-i)!} (Ai)!(Bi)!(Ci)!(Di)!(n4i)!

那么如果 A + B + C + D > n A+B+C+D>n A+B+C+D>n 的话,我们直接枚举,即:

f i = ∑ a = 0 A − i ∑ b = 0 B − i ∑ c = 0 C − i ∑ d = 0 D − i [ a + b + c + d = n − 4 i ] ( n − 4 i ) ! a ! b ! c ! d ! = ( n − 4 i ) ! ∑ a = 0 A − i ∑ b = 0 B − i ∑ c = 0 C − i ∑ d = 0 D − i [ a + b + c + d = n − 4 i ] 1 a ! 1 b ! 1 c ! 1 d ! \begin{aligned} f_i&=\sum_{a=0}^{A-i}\sum_{b=0}^{B-i}\sum_{c=0}^{C-i}\sum_{d=0}^{D-i}[a+b+c+d=n-4i]\frac{(n-4i)!}{a!b!c!d!}\\ &=(n-4i)!\sum_{a=0}^{A-i}\sum_{b=0}^{B-i}\sum_{c=0}^{C-i}\sum_{d=0}^{D-i}[a+b+c+d=n-4i]\frac{1}{a!}\frac{1}{b!}\frac{1}{c!}\frac{1}{d!} \end{aligned} fi=a=0Aib=0Bic=0Cid=0Di[a+b+c+d=n4i]a!b!c!d!(n4i)!=(n4i)!a=0Aib=0Bic=0Cid=0Di[a+b+c+d=n4i]a!1b!1c!1d!1

这是一个四维的卷积,直接 n t t ntt ntt 做就可以了。

时间复杂度 O ( n 2 log ⁡ n ) O(n^2\log n) O(n2logn)


code

#include<cstdio>
#include<vector>
#include<cstring>
#include<algorithm>
#define N 5005
#define P 998244353
using namespace std;
typedef vector<int> poly;
const int g=3;
int n,a,b,c,d,up,ans,pos[N],fac[N],ifac[N];
int add(int x,int y)  {return x+y>=P?x+y-P:x+y;}
int dec(int x,int y)  {return x-y< 0?x-y+P:x-y;}
int mul(int x,int y)  {return 1ll*x*y%P;}
int power(int a,int b){
	int ans=1;
	for(;b;b>>=1,a=mul(a,a))  if(b&1)  ans=mul(ans,a);
	return ans;
}
int L=12,*w[13];
void init_fac(){
	fac[0]=fac[1]=1;
	for(int i=2;i<N;++i)  fac[i]=mul(fac[i-1],i);
	ifac[N-1]=power(fac[N-1],P-2);
	for(int i=N-2;~i;--i)  ifac[i]=mul(ifac[i+1],i+1);
}
int C(int n,int m)  {return mul(fac[n],mul(ifac[m],ifac[n-m]));}
void init_w(){
	for(int i=1;i<=L;++i)
		w[i]=new int[1<<(i-1)];
	int now=power(g,(P-1)/(1<<L));
	w[L][0]=1;
	for(int i=1;i<(1<<(L-1));++i)  w[L][i]=mul(w[L][i-1],now);
	for(int i=L-1;i;--i)
		for(int j=0;j<(1<<(i-1));++j)
			w[i][j]=w[i+1][j<<1];
}
void init(int lim){
	for(int i=0;i<lim;++i)  pos[i]=(pos[i>>1]>>1)|((i&1)*(lim>>1));
}
void NTT(poly &f,int lim,int type){
	for(int i=0;i<lim;++i)
		if(pos[i]>i)  swap(f[i],f[pos[i]]);
	for(int mid=1,l=1;mid<lim;mid<<=1,++l){
		for(int i=0;i<lim;i+=(mid<<1)){
			for(int j=0;j<mid;++j){
				int p0=f[i+j],p1=mul(f[i+j+mid],w[l][j]);
				f[i+j]=add(p0,p1),f[i+j+mid]=dec(p0,p1);
			}
		}
	}
	if(type==-1&&(reverse(f.begin()+1,f.begin()+lim),1)){
		int inv=power(lim,P-2);
		for(int i=0;i<lim;++i)  f[i]=mul(f[i],inv);
	}
}
int calc(int x){
	int ans=C(n-3*x,x);
	poly A(0),B(0),C(0),D(0);
	for(int i=0;i<=a-x;++i)  A.push_back(ifac[i]);
	for(int i=0;i<=b-x;++i)  B.push_back(ifac[i]);
	for(int i=0;i<=c-x;++i)  C.push_back(ifac[i]);
	for(int i=0;i<=d-x;++i)  D.push_back(ifac[i]);
	int lim=1,len=A.size()+B.size()+C.size()+D.size()-3;
	while(lim<len)lim<<=1;init(lim);
	A.resize(lim),NTT(A,lim,1);
	B.resize(lim),NTT(B,lim,1);
	C.resize(lim),NTT(C,lim,1);
	D.resize(lim),NTT(D,lim,1);
	for(int i=0;i<lim;++i)  A[i]=1ll*A[i]*B[i]%P*C[i]%P*D[i]%P;
	NTT(A,lim,-1),A.resize(len);
	return mul(ans,mul(fac[n-4*x],A[n-4*x]));
}
int main(){
	init_w(),init_fac();
	scanf("%d%d%d%d%d%d",&n,&a,&b,&c,&d);
	up=min(min(min(a,b),min(c,d)),n/4);
	for(int i=0;i<=up;++i){
		ans=(i&1)?dec(ans,calc(i)):add(ans,calc(i));
	}
	printf("%d\n",ans);
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值