[TJOI2019] 唱、跳、rap和篮球

\(\text{Problem}:\)[TJOI2019] 唱、跳、rap和篮球

\(\text{Solution}:\)

\(f_{k}\) 表示恰好有 \(k\) 组满足条件,\(g_{k}\) 表示钦定有 \(k\) 组满足条件。考虑枚举最喜欢唱的同学的位置 \(i\),相当于把 \(i,i+1,i+2,i+3\) 捆绑成一个位置,故有:

\[g_{k}=\binom{n-3k}{k}h_{k} \]

其中 \(h_{k}\) 表示每种同学少 \(k\) 人后,从中选出 \(n-4k\) 个同学的组成的排列数。考虑选择若干个最喜欢唱的同学的 \(\text{EGF}\)\(A(x)=\sum\limits_{i=0}^{a-k}\frac{x^{i}}{i!}\),对于其他种类的同学也是同样的定义。显然,对四种同学的 \(\text{EGF}\) 做卷积就是答案的 \(\text{EGF}\)

要求的是 \(f_{0}\)。由二项式反演,有:

\[f_{0}=\sum\limits_{j=0}^{\min(a,b,c,d,\lfloor\frac{n}{4}\rfloor)}(-1)^{j}g_{j} \]

计算出所有 \(g_{j}\) 即可。总时间复杂度 \(O(n^2\log n)\)

观察到每次计算的 \(\text{EGF}\) 在形式上类似,直接做卷积并不优秀。事实上,对于每种同学的 \(\text{EGF}\),当 \(j\)\(1\) 时仅增加了一项。故可以分为两组,组内分别在 \(O(n)\) 的时间内暴力维护卷积。而答案只要求第 \(n-4k\) 项,也可以在 \(O(n)\) 的时间复杂度内求出。总时间复杂度优化至 \(O(n^{2})\)

\(\text{Code}:\)

#include <bits/stdc++.h>
#pragma GCC optimize(3)
//#define int long long
#define ri register
#define mk make_pair
#define fi first
#define se second
#define pb push_back
#define eb emplace_back
#define is insert
#define es erase
#define vi vector<int>
#define vpi vector<pair<int,int>>
using namespace std; const int N=1010, Mod=998244353;
inline int read()
{
	int s=0, w=1; ri char ch=getchar();
	while(ch<'0'||ch>'9') { if(ch=='-') w=-1; ch=getchar(); }
	while(ch>='0'&&ch<='9') s=(s<<3)+(s<<1)+(ch^48), ch=getchar();
	return s*w;
}
int A,B,C,D,n,up;
int fac[N+5],inv[N+5];
int a[N],b[N],c[N],d[N];
int ab[N],cd[N];
inline int ksc(int x,int p) { int res=1; for(;p;p>>=1, x=1ll*x*x%Mod) if(p&1) res=1ll*res*x%Mod; return res; }
inline int Binom(int x,int y) { if(x<y||x<0||y<0) return 0; return 1ll*fac[x]*inv[x-y]%Mod*inv[y]%Mod; }
signed main()
{
	fac[0]=1;
	for(ri int i=1;i<=N;i++) fac[i]=1ll*fac[i-1]*i%Mod;
	inv[N]=ksc(fac[N],Mod-2);
	for(ri int i=N;i;i--) inv[i-1]=1ll*inv[i]*i%Mod;
	n=read(), A=read(), B=read(), C=read(), D=read();
	up=min(n/4,A), up=min(up,B), up=min(up,C), up=min(up,D);
	for(ri int i=0;i<=A-up;i++) a[i]=inv[i];
	for(ri int i=0;i<=B-up;i++) b[i]=inv[i];
	for(ri int i=0;i<=C-up;i++) c[i]=inv[i];
	for(ri int i=0;i<=D-up;i++) d[i]=inv[i];
	for(ri int i=0;i<=A-up;i++)
	for(ri int j=0;j<=B-up;j++)
	if(i+j<=n) ab[i+j]=(ab[i+j]+1ll*a[i]*b[j]%Mod)%Mod;
	for(ri int i=0;i<=C-up;i++)
	for(ri int j=0;j<=D-up;j++)
	if(i+j<=n) cd[i+j]=(cd[i+j]+1ll*c[i]*d[j]%Mod)%Mod;
	int ans=0;
	for(ri int i=up;~i;i--)
	{
		int w=0;
		for(ri int j=0;j<=n-i*4;j++) w=(w+1ll*ab[j]*cd[n-i*4-j]%Mod)%Mod;
		w=1ll*w*Binom(n-i*3,i)%Mod*fac[n-i*4]%Mod;
		if(i&1) ans=(ans-w+Mod)%Mod;
		else ans=(ans+w)%Mod;
		if(!i) break;
		a[A-i+1]=inv[A-i+1];
		b[B-i+1]=inv[B-i+1];
		c[C-i+1]=inv[C-i+1];
		d[D-i+1]=inv[D-i+1];
		for(ri int j=0;j<=B-i;j++) if(j+A-i+1<=n) ab[j+A-i+1]=(ab[j+A-i+1]+1ll*a[A-i+1]*b[j]%Mod)%Mod;
		for(ri int j=0;j<=A-i;j++) if(j+B-i+1<=n) ab[j+B-i+1]=(ab[j+B-i+1]+1ll*a[j]*b[B-i+1]%Mod)%Mod;
		if(A-i+1+B-i+1<=n) ab[A-i+1+B-i+1]=(ab[A-i+1+B-i+1]+1ll*a[A-i+1]*b[B-i+1]%Mod)%Mod;
		for(ri int j=0;j<=D-i;j++) if(j+C-i+1<=n) cd[j+C-i+1]=(cd[j+C-i+1]+1ll*c[C-i+1]*d[j]%Mod)%Mod;
		for(ri int j=0;j<=C-i;j++) if(j+D-i+1<=n) cd[j+D-i+1]=(cd[j+D-i+1]+1ll*c[j]*d[D-i+1]%Mod)%Mod;
		if(C-i+1+D-i+1<=n) cd[C-i+1+D-i+1]=(cd[C-i+1+D-i+1]+1ll*c[C-i+1]*d[D-i+1]%Mod)%Mod;
	}
	printf("%d\n",ans);
	return 0;
}
posted @ 2021-05-07 21:01  zkdxl  阅读(51)  评论(0编辑  收藏  举报