【洛谷5339】[TJOI2019] 唱、跳、rap和篮球(容斥+NTT)

点此看题面

大致题意:(4)种人分别喜欢唱、跳、rap、篮球,且这(4)种人各有(a,b,c,d)个,现要从中选出(n)个人组成一个排列,使得不存在连续(4)个人按序分别喜欢唱、跳、rap、篮球,求方案数。

容斥

看到这题显然首先可以想到容斥。

我们设(S(k))表示有至少(k)组人满足其按序分别喜欢唱、跳、rap、篮球

则答案就是:

[sum_{k=0}^{min(lfloorfrac n4 floor,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}^ksum_{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}^kcdot(n-4k)!cdotsum_{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!}cdotfrac1{j!}cdotfrac1{p!}cdotfrac1{q!}) ]

显然,前面的(C_{n-3k}^kcdot(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;
}
原文地址:https://www.cnblogs.com/chenxiaoran666/p/Luogu5339.html