[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;
}
夜畔流离回,暗叹永无殿。
独隐万花翠,空寂亦难迁。
千秋孰能为,明灭常久见。
但得心未碎,踏遍九重天。