
容易想到的是,枚举讨论蔡徐坤的组数,设至少有组讨论蔡徐坤的人方案数是,容斥一下,答案就是。
现在的主要问题是求出,将讨论蔡徐坤的四个人缩成一个组,所以这样的组数是组,剩下的人数。
于是问题转化为一个长度为的序列,往里面放个讨论蔡徐坤的组,剩下放入个喜欢唱的,,放入个喜欢跳的,,……,满足。(即放满)
放入个讨论蔡徐坤的组,方案数是。
剩下如何处理唱跳rap篮球呢?
第一种方法:
放入个喜欢唱的,方法数,剩下个空位。
放入个喜欢跳的,方法数,剩下个空位。
放入个喜欢rap的,方法数,由于确定了,剩下的即确定,后面的不用枚举。
令。
答案方案数。
但是注意到我们要枚举,时间复杂度,过不去。
就算是前缀和优化,时间复杂度,还是gg。
第二种方法:
不妨把喜欢唱和喜欢跳的放在一起考虑。
先枚举他们加起来的总数,方案数。
再枚举,方案数,同理,确定了,剩下的就确定了,所以不用枚举。
剩下的喜欢rap的和喜欢篮球的,总数也确定为
枚举,方案数。
这样答案就是
为什么是,因为的取值不能小于,否则喜欢跳的超过,不行。
注意到这样我们还是要枚举三个变量 ,时间复杂度,gg。
不妨考虑固定住,只考虑后面一坨。
答案就是
后面两坨都可以通过前缀和算出,所以总时间复杂度,可以。
总结,可以通过类似于折半搜索的方法减少时间复杂度。
#include <bits/stdc++.h>
#define MOD 998244353
#define MAXN 1005
using namespace std;
inline int read(){
int x=0,f=1;
char ch=getchar();
while (ch<'0'||ch>'9'){
if (ch=='-') f=-1;
ch=getchar();
}
while (ch>='0'&&ch<='9'){
x=(x<<1)+(x<<3)+(ch^'0');
ch=getchar();
}
return x*f;
}
int n,a,b,c,d;
int C[MAXN][MAXN],sum[MAXN][MAXN];
inline void Init(){
for (register int i=0;i<=n;++i){
C[i][0]=sum[i][0]=1;
for (register int j=1;j<=i;++j){
C[i][j]=(C[i-1][j]+C[i-1][j-1])%MOD;
}
for (register int j=1;j<=n;++j){
sum[i][j]=(sum[i][j-1]+C[i][j])%MOD;
}
}
}
inline long long QuerySum(int i,int l,int r){
if (l>r) return 0;
if (l<=0) return sum[i][r];
return (sum[i][r]-sum[i][l-1]+MOD)%MOD;
}
int main(){
n=read();
a=min(n,read());b=min(n,read());c=min(n,read());d=min(n,read());//超过n和n取min,因为任何一种都不可能用超过n次
Init();
int ans=0;
for (register int cxk=0;cxk<=n/4ll;++cxk){//cxk是讨论cxk的组数
int ret=0;
int left=n-cxk*4;
for (register int i=0;i<=left;++i){//从剩下的人里面选出i个人讨论唱跳,剩下讨论rap篮球
int j=left-i;//讨论rap和篮球
ret=(ret+(long long)C[left][i]*QuerySum(i,i-b,a)%MOD*QuerySum(j,j-d,c)%MOD)%MOD;
}
ret=((long long)ret*C[cxk+left][cxk])%MOD;
if (cxk&1) ans=(ans-ret+MOD)%MOD;
else ans=(ans+ret)%MOD;
--a,--b,--c,--d;
if (a<0||b<0||c<0||d<0) break;
}
printf("%d\n",ans);
}