【TJOI2019】唱跳rap篮球 【容斥】【多项式】

10 篇文章 0 订阅
5 篇文章 0 订阅

传送门

(updateeee 忘了最后要减去了,,直接把错误答案的公式留在了这里2019.10.13)

ldx说我容斥做少了,我觉得说的真tm对,来做做容斥。

首先考虑正难则反。如果没有ctrl不好算,我们算有的。

如果有i个ctrl,将这4个人缩成一个点,加上其他人有n-3i个点。从中随便找i个都可以作为ctrl。

所以首先,i个ctrl的方案有\binom{n-3i}{i}

但是注意,其它空位放的人也可能有ctrl。

所以我们改i的意义为至少有i对ctrl。

接着发现在算i=1的时候,i=2要算2遍,3三遍,i=2的时候,3算3遍,4算6遍。

大概是这个意思

XX。。。。

X。。。。

。X。。。。

2遍。

XXX。。。。

X。。。。

。X。。。

。。X。。。

如此,任何一种更大的j的方式,都会被小的i给算个很多遍。

也就是说算i的时候,j要算\binom{j}{i}遍。

所以我们要构造一个容斥式子使得所有算了j这个方案的系数合起来是1。

有一个很tm巧妙的式子是

\sum_{i=1}^n(-1)^{i-1}\binom{n}{i}=1

考虑(1+(-1))^n的符号

+-+-+-+-,,,

因为式子中-1的次方是i-1,所以变成了

-+-+-+-+,,,

又没算第一项。也就是说少算了第一个-1,那答案就是1。

运用这种跟组合有关的前缀式子,令G(i)表示i个位子随便乱弄的方案,我们可以将答案表示成

ans=\sum_{i=1}^n(-1)^{i-1}\binom{n-3i}{i}G(n-4i)

所以我们要处理G。因为在乱弄的时候要考虑相同的东西放不同位置答案是一样的,而且东西的数量还有限制。

如果现在还能使用的人数少于n-4i,那贡献肯定是0.放都放不完。如果n<0了,没地方放也是一样。

当剩下的大于等于n-4i时

G(n-4i)=(n-4i)!\sum_{a<=a_0-i,b<=b_0-i,c<=c_0-i,d<=d_0-i}[a+b+c+d==n-4i]\frac{1}{a!b!c!d!}

为什么呢,剩下的位子随便放是前面的阶乘。但同样的东西交换不算答案,所以还要消去同样东西的顺序。并且还要符合数量合。

反正只要一个位子的答案,直接卷积就是了。

所以我们得到的非法方案总共为

\sum_{i=1}^n(-1)^{i-1}\binom{n-3i}{i}(n-4i)!\sum_{a<=a_0-i,b<=b_0-i,c<=c_0-i,d<=d_0-i}[a+b+c+d==n-4i]\frac{1}{a!b!c!d!}

然后用总数n!一减得到的式子应该是

ans=\sum_{i=0}^n(-1)^{i}\binom{n-3i}{i}(n-4i)!\sum_{a<=a_0-i,b<=b_0-i,c<=c_0-i,d<=d_0-i}[a+b+c+d==n-4i]\frac{1}{a!b!c!d!}

也就是前面添加了i=0项,然后-1符号反一下。

以上。

	#include<bits/stdc++.h>
	using namespace std;
	#define in read()
	#define int long long
	int in{
		int cnt=0,f=1;char ch=0;
		while(!isdigit(ch)){
			ch=getchar();if(ch=='-')f=-1;
		}
		while(isdigit(ch)){
			cnt=cnt*10+ch-48;
			ch=getchar(); 
		}return cnt*f;
	}const int mod=998244353;
	int limit,l,r[1000003],a[1000003],b[1000003],c[1000003],d[1000003];
	int ksm(int a,int b){
		int sum=1;
		while(b){
			if(b&1)sum=sum*a%mod;a=a*a%mod;b>>=1;
		}return sum;
	}
	void getl(int len){
		limit=1,l=0;
		while(limit<=len)limit<<=1,l++;
		for(int i=0;i<limit;i++)r[i]=(r[i>>1]>>1)|((i&1)<<(l-1)); 
	}
	void NTT(int *a,int len,int inv){
		for(int i=0;i<len;i++)if(i<r[i])swap(a[i],a[r[i]]);
		for(int mid=1;mid<len;mid<<=1){
			int wn=ksm(3,(mod-1)/(mid<<1));
			for(int i=0;i<len;i+=mid*2){
				int omega=1;
				for(int j=0;j<mid;j++,omega=omega*wn%mod){
					int x=a[i+j],y=a[i+j+mid]*omega%mod;
					a[i+j]=(x+y)%mod;a[i+j+mid]=(x-y+mod)%mod;
				}
			}
		}if(inv==1)return;
		reverse(a+1,a+len);int gu=ksm(len,mod-2);
		for(int i=0;i<len;i++)a[i]=a[i]*gu%mod;
	}
	
	int fac[1000003],ifac[1000003];
	int Q(int N,int A,int B,int C,int D){
		if(N>A+B+C+D)return 0;
		if(N<0)return 0;
		getl((A+B+C+D)<<1);
		for(int i=0;i<limit;i++)a[i]=(i<=A)?ifac[i]:0;
		for(int i=0;i<limit;i++)b[i]=(i<=B)?ifac[i]:0;
		for(int i=0;i<limit;i++)c[i]=(i<=C)?ifac[i]:0;
		for(int i=0;i<limit;i++)d[i]=(i<=D)?ifac[i]:0;
		NTT(a,limit,1);NTT(b,limit,1);NTT(c,limit,1);NTT(d,limit,1);
		for(int i=0;i<limit;i++)a[i]=a[i]*b[i]%mod*c[i]%mod*d[i]%mod;
		NTT(a,limit,-1);return fac[N]*a[N]%mod;
	}int cc[2003][2003];int n,A,B,C,D;
	signed main(){fac[0]=ifac[0]=1;
		n=in;A=in;B=in;C=in;D=in;for(int i=0;i<=2000;i++)cc[i][0]=1;
		for(int i=1;i<=2000;i++)for(int j=1;j<=2000;j++)cc[i][j]=(cc[i-1][j]+cc[i-1][j-1])%mod;
		int minn=0x3f3f3f3f;minn=min(n/4,min(A,min(B,min(C,D))));
		for(int i=1;i<=1000000;i++)fac[i]=fac[i-1]*i%mod;
		ifac[1000000]=ksm(fac[1000000],mod-2);
		for(int i=999999;i>=1;i--)ifac[i]=ifac[i+1]*(i+1)%mod;
		int ans=0;
		for(int i=0,_1=1;i<=minn;i++){
			ans=(ans+_1*cc[n-i*3][i]%mod*Q(n-4*i,A-i,B-i,C-i,D-i)%mod)%mod;
			_1=_1*(mod-1)%mod;
		}
		//cout<<cc[n-3][1]<<" "<<Q(n-4,A-1,B-1,C-1,D-1)<<endl;
		cout<<ans;
		return 0;
	}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值