题解 P5339 【[TJOI2019]唱、跳、rap和篮球】
在我的博客查看
题解
没学过生成函数,暴力容斥了。
感觉这种在序列上的组合问题会一下子想到容斥。
用 c,t,r,l 分别表示喜欢唱、跳、rap、篮球的同学。
首先,如果要用容斥解决的话,可以从序列中连续的 c,t,r,l 的个数入手。
首先需要计算当 c,t,r,l 中任意一个同学时,长为 c,t,r,l 的方案。
定义
而根据组合意义,
而在本题中,
递推时,只需要处理
有了所有的
当序列中有至少 c,t,r,l 时,所有位置上都可以任选。
这是一个可重组合数问题,但是由于只有四种元素,我们可以先确定四种喜好的同学分别有多少个。四种喜好的人数依次设为
那么接下来就是求合法的(
直接枚举是
我们可以通过枚举二元组
也就是说,如果用
上面解释了一个 meet-in-middle 的思路,复杂度为
这时,我们得到了合法的四元组
但是我们只知道数目,不知道具体的
我们可以在上面算贡献的时候,对于二元组
这样就有了“至少
至少
依此类推,直到至少 c,t,r,l 中最少的一个用完,或者至少 c,t,r,l 填满了所有位置。
答案就是至少
时间复杂度
trick:
从这题可以看出来组合数的一种推法。当间隔不大于
Code:
#include<cstdio>
#include<cstring>
#define p 998244353
#define ll long long
int Min(int x,int y){return x<y?x:y;}
ll qpow(ll x,ll y)
{
ll ans=1;
while(y)
{
if(y&1)
ans=ans*x%p;
x=x*x%p;
y>>=1;
}
return ans;
}
int f[1010][300];
ll sum[300],func[1010],inv[1010];
int cnt[1010];
int main()
{
int n;
scanf("%d",&n);
func[0]=1;
f[0][0]=1;
for(int i=1;i<=n;++i)
{
func[i]=func[i-1]*i%p;
f[i][0]=1;
for(int j=1;j<=i/4;++j)
{
sum[j-1]=(sum[j-1]+f[i-4][j-1])%p;
f[i][j]=sum[j-1];
}
}
inv[n]=qpow(func[n],p-2);
for(int i=n-1;i>=0;--i)
inv[i]=inv[i+1]*(i+1)%p;
int a,b,c,d;
scanf("%d%d%d%d",&a,&b,&c,&d);
int mn=a<b?(a<c?(a<d?a:d):(c<d?c:d)):(b<c?(b<d?b:d):(c<d?c:d)),ans=0;
//int mn=Min(Min(a,b),Min(c,d)),ans=0;
for(int i=0,tot=n;i<=mn&&i<=n/4;++i,--a,--b,--c,--d,tot-=4)
{
memset(cnt,0,sizeof(cnt));
long long op=0;
for(int A=0;A<=a;++A)
for(int B=0;B<=b;++B)
cnt[A+B]=(cnt[A+B]+inv[A]*inv[B])%p;
for(int C=0;C<=c;++C)
for(int D=0;D<=d&&C+D<=tot;++D)
op=(op+cnt[tot-C-D]*inv[C]%p*inv[D])%p;
op=op*func[tot]%p;
if(i&1)
ans=(ans+p-op*f[n][i]%p)%p;
else
ans=(ans+op*f[n][i]%p)%p;
}
printf("%d\n",ans);
return 0;
}