zoukankan      html  css  js  c++  java
  • [TJOI2019]唱、跳、rap和篮球_生成函数_容斥原理_ntt

    [TJOI2019]唱、跳、rap和篮球

    这么多人过没人写题解啊

    那我就随便说说了嗷

    这题第一步挺套路的,就是题目要求不能存在balabala的时候考虑正难则反,要求必须存在的方案数然后用总数减,往往更简单。

    这个题呢直接要求存在发现还不咋好求,反正就是存在嘛我们就容斥好了。

    呐,我们就枚举至少有多少段(唱跳rap篮球)。

    假设有(i)段,那么枚举一下这(i)段的位置,这是(inom{n-3i}{i})的。

    就相当于给定长度为(n-3i)的空格,选出(i)个空格为(唱跳rap篮球)的起始点就好了。

    假设每种人分别有(num[1])(num[4])个,把他们都(-i),就相当于求剩下这些人随意排列的方案数咯。

    假设第(i)种人用了(now[i])个,那么方案数为
    (frac{(n-4i)!}{prod now[j]!})

    这东西就是拿生成函数搞一搞就好了。

    就是第一个人的生成函数是(sumlimits_{i=0}^{num[1]} frac{x^i}{i!})

    把这四个生成函数乘一起,最后返回第(n-4i)项乘以((n-4i)!)就好啦。

    贴个代码

    #include <bits/stdc++.h>
    #define N 1010 
    using namespace std;
    typedef long long ll;
    const int mod = 998244353 ;
    int C[N][N],num[5],len[5],fac[N<<2],inv[N<<2],b[5][N<<2];
    int n;
    char *p1,*p2,buf[100000];
    #define nc() (p1==p2&&(p2=(p1=buf)+fread(buf,1,100000,stdin),p1==p2)?EOF:*p1++)
    int rd() {int x=0,f=1; char c=nc(); while(c<48) {if(c=='-') f=-1; c=nc();} while(c>47) x=(((x<<2)+x)<<1)+(c^48),c=nc(); return x*f;}
    int qpow(int x,int y)
    {
    	int ans=1;
    	while(y)
    	{
    		if(y&1) ans=(ll)ans*x%mod;
    		y>>=1;
    		x=(ll)x*x%mod;
    	}
    	return ans;
    }
    void init()
    {
    	fac[0]=1; for(int i=1;i<=1000;i++) fac[i]=(ll)fac[i-1]*i%mod;
    	inv[0]=1; for(int i=1;i<=1000;i++) inv[i]=qpow(fac[i],mod-2);
    }
    void ntt(int *a,int len,int flg)
    {
    	int i,j,k,t,w,x,tmp;
    	for(i=k=0;i<len;i++)
    	{
    		if(i>k) swap(a[i],a[k]);
    		for(j=len>>1;(k^=j)<j;j>>=1);
    	}
    	for(k=2;k<=len;k<<=1)
    	{
    		t=k>>1;
    		x=qpow(3,(mod-1)/k);
    		if(flg==-1) x=qpow(x,mod-2);
    		for(i=0;i<len;i+=k)
    			for(j=i,w=1;j<i+t;j++)
    			{
    				tmp=(ll)a[j+t]*w%mod;
    				a[j+t]=(a[j]-tmp+mod)%mod;
    				a[j]=(a[j]+tmp)%mod;
    				w=(ll)w*x%mod;
    			}
    	}
    	if(flg==-1) for(t=qpow(len,mod-2),i=0;i<len;i++) a[i]=(ll)a[i]*t%mod;
    }
    int calc(int k)
    {
    	int l=1;
    	while(l<=max((num[4]-k)<<1,(n-4*k)<<1)) l<<=1;
    	while(l<=(num[4]<<2)) l<<=1;
    	for(int i=1;i<=4;i++)
    	{
    		len[i]=num[i]-k;
    		for(int j=0;j<=l;j++) b[i][j]=0;
    	}
    	for(int i=1;i<=4;i++) for(int j=0;j<=len[i];j++) b[i][j]=inv[j];
    	for(int i=1;i<=4;i++) ntt(b[i],l,1);
    	for(int i=2;i<=4;i++) for(int j=0;j<l;j++) b[1][j]=(ll)b[1][j]*b[i][j]%mod;
    	ntt(b[1],l,-1);
    	return (ll)b[1][n-4*k]*fac[n-4*k]%mod;
    }
    int main()
    {
    	n=rd(); for(int i=1;i<=4;i++) num[i]=rd();
    	for(int i=0;i<=n;i++)
    	{
    		C[i][0]=1;
    		for(int j=1;j<=i;j++) C[i][j]=(C[i-1][j]+C[i-1][j-1])%mod;
    	}
    	sort(num+1,num+5);
    	int l=1;
    	while(l<=(num[4]<<2)) l<<=1;
    	init();
    	int ans=0;
    	for(int i=0;(i<<2)<=min(n,num[1]<<2);i++)
    	{
    		int mdl=(ll)calc(i)*C[n-3*i][i]%mod;
    		if(i&1) (ans-=mdl)%=mod;
    		else (ans+=mdl)%=mod;
    	}
    	printf("%d
    ",(ans+mod)%mod);
    	return 0;
    }
    
  • 相关阅读:
    iou与giou对比
    Linux学习第一天 vim
    奖励加分申请
    人月神话阅读笔记3
    5.27
    5.26
    5.25
    5.23
    5.22
    5.21
  • 原文地址:https://www.cnblogs.com/ShuraK/p/10805548.html
Copyright © 2011-2022 走看看