把博客园图标替换成自己的图标
把博客园图标替换成自己的图标end

【洛谷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;
}
posted @ 2019-12-23 20:24  TheLostWeak  阅读(265)  评论(0编辑  收藏  举报