【洛谷5339】[TJOI2019] 唱、跳、rap和篮球(容斥+NTT)
大致题意: 有\(4\)种人分别喜欢唱、跳、rap、篮球,且这\(4\)种人各有\(a,b,c,d\)个,现要从中选出\(n\)个人组成一个排列,使得不存在连续\(4\)个人按序分别喜欢唱、跳、rap、篮球,求方案数。
容斥
看到这题显然首先可以想到容斥。
我们设\(S(k)\)表示有至少\(k\)组人满足其按序分别喜欢唱、跳、rap、篮球。
则答案就是:
\[\sum_{k=0}^{min(\lfloor\frac n4\rfloor,a,b,c,d)}(-1)^kS(k)
\]
现在考虑如何求出\(S(k)\)。
显然在\(n\)个人中选择\(k\)组人使其满足条件,就是选出\(k\)组连续的\(4\)个位置,而因为要求连续,所以这\(4\)个位置可以看作\(1\)个,即方案数为\(C_{n-3k}^k\)。
而除去这\(4k\)个位置后,剩下的位置可以随便填。
如果我们枚举\(i,j,p,q\)分别表示除去这\(4k\)个位置后\(4\)种人的个数,则此时填法的方案数就是一个可重全排列。
即:
\[S(k)=C_{n-3k}^k\sum_{i=0}^{a-k}\sum_{j=0}^{b-k}\sum_{p=0}^{c-k}\sum_{q=0}^{d-k}[i+j+p+q=n-4k]\frac{(n-4k)!}{i!j!p!q!}
\]
这个式子看似很难求,但其实我们把它稍微转化一下,你就会觉得它很熟悉:
\[S(k)=C_{n-3k}^k\cdot(n-4k)!\cdot\sum_{i=0}^{a-k}\sum_{j=0}^{b-k}\sum_{p=0}^{c-k}\sum_{q=0}^{d-k}[i+j+p+q=n-4k](\frac1{i!}\cdot\frac1{j!}\cdot\frac1{p!}\cdot\frac1{q!})
\]
显然,前面的\(C_{n-3k}^k\cdot(n-4k)!\)在枚举\(k\)的情况下,可以通过预处理\(O(1)\)求出。
而后面的式子中,\(\frac1{i!},\frac1{j!},\frac1{p!},\frac1{q!}\)分别是只与\(i,j,p,q\)有关的式子,且我们只需要求出\(i+j+p+q=n-4k\)时的值。
你想到了什么?这不就是\(4\)个多项式的卷积嘛!
于是套上个\(NTT\)就做完了。
代码
#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 1000
#define X 998244353
#define C(x,y) (1LL*Fac[x]*IFac[y]%X*IFac[(x)-(y)]%X)
using namespace std;
int n,a,b,c,d,s[N+5],Fac[N+5],IFac[N+5];
I int Qpow(RI x,RI y) {RI t=1;W(y) y&1&&(t=1LL*t*x%X),x=1LL*x*x%X,y>>=1;return t;}//快速幂
template<int SZ,int PR> class Poly//多项式算法
{
private:
int IPR,P,L,R[4*N+5],A[4*N+5],B[4*N+5],C[4*N+5],D[4*N+5];
I void T(int *s,CI op)
{
RI i,j,k,U,S,x,y;for(i=0;i^P;++i) i<R[i]&&(x=s[i],s[i]=s[R[i]],s[R[i]]=x);
for(i=1;i^P;i<<=1) for(U=Qpow(~op?PR:IPR,(X-1)/(i<<1)),j=0;j^P;j+=i<<1)
for(S=1,k=0;k^i;++k,S=1LL*S*U%X) s[j+k]=((x=s[j+k])+(y=1LL*S*s[i+j+k]%X))%X,s[i+j+k]=(x-y+X)%X;
}
public:
I Poly() {IPR=Qpow(PR,X-2);}
I int NTT(CI n,CI a,CI b,CI c,CI d)//求出长度为a,b,c,d的四个多项式卷积的n次项的系数
{
RI i;P=1,L=0;W(P<=a+b+c+d) P<<=1,++L;for(i=0;i^P;++i) R[i]=(R[i>>1]>>1)|((i&1)<<L-1);
for(i=0;i<=a;++i) A[i]=IFac[i];for(;i<P;++i) A[i]=0;
for(i=0;i<=b;++i) B[i]=IFac[i];for(;i<P;++i) B[i]=0;
for(i=0;i<=c;++i) C[i]=IFac[i];for(;i<P;++i) C[i]=0;
for(i=0;i<=d;++i) D[i]=IFac[i];for(;i<P;++i) D[i]=0;
for(T(A,1),T(B,1),T(C,1),T(D,1),i=0;i^P;++i) A[i]=1LL*A[i]*B[i]%X*C[i]%X*D[i]%X;//NTT
return T(A,-1),1LL*A[n]*Qpow(P,X-2)%X;//返回答案
}
};Poly<N,3> P;
int main()
{
RI i;for(scanf("%d%d%d%d%d",&n,&a,&b,&c,&d),Fac[0]=i=1;i<=n;++i) Fac[i]=1LL*Fac[i-1]*i%X;//预处理阶乘
for(IFac[n]=Qpow(Fac[n],X-2),i=n-1;~i;--i) IFac[i]=1LL*IFac[i+1]*(i+1)%X;//预处理阶乘逆元
RI j,ans=0,lim=min(n/4,min(min(a,b),min(c,d)));for(i=0;i<=lim;++i)//容斥
ans=(1LL*(i&1?X-1:1)*C(n-3*i,i)%X*Fac[n-4*i]%X*P.NTT(n-4*i,a-i,b-i,c-i,d-i)+ans)%X;//计算答案
return printf("%d",ans),0;
}
待到再迷茫时回头望,所有脚印会发出光芒