• 【洛谷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;
    }
    
  • 相关阅读:
    [JSBSim]基于winsocket2的TCPUDP使用例子
    [转][JSBSim]JSBSim的使用--飞行控制组件及其配置
    [原][译][osg][osgEarth]飞行模拟软件JSBSim的操作(FGFCS类)
    [原][JSBSim]基于qt代码实现:TCP|UDP与飞行模拟软件JSBSim的通信,现实模型飞行!
    Linux mysql添加用户,删除用户,以及用户权限
    Linux启动/停止/重启Mysql数据库的方法
    Linux中vi编辑器的使用详解
    安装mysql问题解决
    centos6.5下修改文件夹权限和用户名用户组
    mysql启动时报错:Starting MySQL... ERROR! The server quit without updating PID file (/opt/mysql/data/mysql.pid) 的解决方法
  • 原文地址:https://www.cnblogs.com/chenxiaoran666/p/Luogu5339.html
Copyright © 2020-2023  润新知